From abb7d134c02ac60091108c491dafb00877093170 Mon Sep 17 00:00:00 2001 From: John Hoford Date: Mon, 24 Feb 2014 15:21:42 -0800 Subject: add Dynamic Drawable (vector graphics for Icons) Change-Id: Id44b7f6d1b8dd073b8ed35e2142a380550f2449b --- api/current.txt | 44 +- core/res/res/values/attrs.xml | 139 ++ core/res/res/values/public.xml | 19 + .../java/android/graphics/drawable/Drawable.java | 2 + .../android/graphics/drawable/VectorDrawable.java | 1955 ++++++++++++++++++++ tests/DynamicDrawableTest/Android.mk | 26 + tests/DynamicDrawableTest/AndroidManifest.xml | 99 + tests/DynamicDrawableTest/lint.xml | 3 + .../DynamicDrawableTest/res/drawable-hdpi/icon.png | Bin 0 -> 5141 bytes tests/DynamicDrawableTest/res/drawable/icon.png | Bin 0 -> 3133 bytes .../res/drawable/vector_drawable01.xml | 73 + .../res/drawable/vector_drawable02.xml | 46 + .../res/drawable/vector_drawable03.xml | 137 ++ .../res/drawable/vector_drawable04.xml | 126 ++ .../res/drawable/vector_drawable05.xml | 147 ++ .../res/drawable/vector_drawable06.xml | 76 + .../res/drawable/vector_drawable07.xml | 45 + .../res/drawable/vector_drawable08.xml | 48 + .../res/drawable/vector_drawable09.xml | 41 + .../res/drawable/vector_drawable10.xml | 114 ++ .../res/drawable/vector_drawable11.xml | 62 + .../res/drawable/vector_drawable12.xml | 90 + .../res/drawable/vector_drawable13.xml | 82 + .../res/drawable/vector_drawable14.xml | 54 + .../res/drawable/vector_drawable15.xml | 48 + .../res/drawable/vector_drawable16.xml | 46 + .../res/drawable/vector_drawable17.xml | 46 + .../res/drawable/vector_drawable18.xml | 45 + .../res/drawable/vector_drawable19.xml | 45 + .../res/drawable/vector_drawable20.xml | 37 + .../res/drawable/vector_icon_create.xml | 29 + .../res/drawable/vector_icon_delete.xml | 29 + .../res/drawable/vector_icon_heart.xml | 29 + .../res/drawable/vector_icon_schedule.xml | 33 + .../res/drawable/vector_icon_settings.xml | 29 + .../res/drawable/vector_test01.xml | 31 + .../res/drawable/vector_test02.xml | 31 + tests/DynamicDrawableTest/res/values/strings.xml | 18 + tests/DynamicDrawableTest/res/values/styles.xml | 16 + .../com/android/test/dynamic/VectorCheckbox.java | 69 + .../com/android/test/dynamic/VectorDrawable01.java | 89 + .../test/dynamic/VectorDrawableDupPerf.java | 43 + .../test/dynamic/VectorDrawablePerformance.java | 95 + .../test/dynamic/VectorDrawableStaticPerf.java | 36 + .../android/test/dynamic/VectorDrawableTest.java | 152 ++ .../android/test/dynamic/VectorPathChecking.java | 25 + 46 files changed, 4447 insertions(+), 2 deletions(-) create mode 100644 graphics/java/android/graphics/drawable/VectorDrawable.java create mode 100644 tests/DynamicDrawableTest/Android.mk create mode 100644 tests/DynamicDrawableTest/AndroidManifest.xml create mode 100644 tests/DynamicDrawableTest/lint.xml create mode 100644 tests/DynamicDrawableTest/res/drawable-hdpi/icon.png create mode 100644 tests/DynamicDrawableTest/res/drawable/icon.png create mode 100644 tests/DynamicDrawableTest/res/drawable/vector_drawable01.xml create mode 100644 tests/DynamicDrawableTest/res/drawable/vector_drawable02.xml create mode 100644 tests/DynamicDrawableTest/res/drawable/vector_drawable03.xml create mode 100644 tests/DynamicDrawableTest/res/drawable/vector_drawable04.xml create mode 100644 tests/DynamicDrawableTest/res/drawable/vector_drawable05.xml create mode 100644 tests/DynamicDrawableTest/res/drawable/vector_drawable06.xml create mode 100644 tests/DynamicDrawableTest/res/drawable/vector_drawable07.xml create mode 100644 tests/DynamicDrawableTest/res/drawable/vector_drawable08.xml create mode 100644 tests/DynamicDrawableTest/res/drawable/vector_drawable09.xml create mode 100644 tests/DynamicDrawableTest/res/drawable/vector_drawable10.xml create mode 100644 tests/DynamicDrawableTest/res/drawable/vector_drawable11.xml create mode 100644 tests/DynamicDrawableTest/res/drawable/vector_drawable12.xml create mode 100644 tests/DynamicDrawableTest/res/drawable/vector_drawable13.xml create mode 100644 tests/DynamicDrawableTest/res/drawable/vector_drawable14.xml create mode 100644 tests/DynamicDrawableTest/res/drawable/vector_drawable15.xml create mode 100644 tests/DynamicDrawableTest/res/drawable/vector_drawable16.xml create mode 100644 tests/DynamicDrawableTest/res/drawable/vector_drawable17.xml create mode 100644 tests/DynamicDrawableTest/res/drawable/vector_drawable18.xml create mode 100644 tests/DynamicDrawableTest/res/drawable/vector_drawable19.xml create mode 100644 tests/DynamicDrawableTest/res/drawable/vector_drawable20.xml create mode 100644 tests/DynamicDrawableTest/res/drawable/vector_icon_create.xml create mode 100644 tests/DynamicDrawableTest/res/drawable/vector_icon_delete.xml create mode 100644 tests/DynamicDrawableTest/res/drawable/vector_icon_heart.xml create mode 100644 tests/DynamicDrawableTest/res/drawable/vector_icon_schedule.xml create mode 100644 tests/DynamicDrawableTest/res/drawable/vector_icon_settings.xml create mode 100644 tests/DynamicDrawableTest/res/drawable/vector_test01.xml create mode 100644 tests/DynamicDrawableTest/res/drawable/vector_test02.xml create mode 100644 tests/DynamicDrawableTest/res/values/strings.xml create mode 100644 tests/DynamicDrawableTest/res/values/styles.xml create mode 100644 tests/DynamicDrawableTest/src/com/android/test/dynamic/VectorCheckbox.java create mode 100644 tests/DynamicDrawableTest/src/com/android/test/dynamic/VectorDrawable01.java create mode 100644 tests/DynamicDrawableTest/src/com/android/test/dynamic/VectorDrawableDupPerf.java create mode 100644 tests/DynamicDrawableTest/src/com/android/test/dynamic/VectorDrawablePerformance.java create mode 100644 tests/DynamicDrawableTest/src/com/android/test/dynamic/VectorDrawableStaticPerf.java create mode 100644 tests/DynamicDrawableTest/src/com/android/test/dynamic/VectorDrawableTest.java create mode 100644 tests/DynamicDrawableTest/src/com/android/test/dynamic/VectorPathChecking.java diff --git a/api/current.txt b/api/current.txt index 2ba5e5c..1ea4f14 100644 --- a/api/current.txt +++ b/api/current.txt @@ -287,6 +287,7 @@ package android { field public static final int alwaysDrawnWithCache = 16842991; // 0x10100ef field public static final int alwaysRetainTaskState = 16843267; // 0x1010203 field public static final int angle = 16843168; // 0x10101a0 + field public static final int animate = 16843794; // 0x1010412 field public static final int animateFirstView = 16843477; // 0x10102d5 field public static final int animateLayoutChanges = 16843506; // 0x10102f2 field public static final int animateOnClick = 16843356; // 0x101025c @@ -373,6 +374,7 @@ package android { field public static final int clipChildren = 16842986; // 0x10100ea field public static final int clipOrientation = 16843274; // 0x101020a field public static final int clipToPadding = 16842987; // 0x10100eb + field public static final int clipToPath = 16843793; // 0x1010411 field public static final int codes = 16843330; // 0x1010242 field public static final int collapseColumns = 16843083; // 0x101014b field public static final int color = 16843173; // 0x10101a5 @@ -465,6 +467,7 @@ package android { field public static final int dropDownWidth = 16843362; // 0x1010262 field public static final int duplicateParentState = 16842985; // 0x10100e9 field public static final int duration = 16843160; // 0x1010198 + field public static final int durations = 16843786; // 0x101040a field public static final int editTextBackground = 16843602; // 0x1010352 field public static final int editTextColor = 16843601; // 0x1010351 field public static final int editTextPreferenceStyle = 16842898; // 0x1010092 @@ -509,9 +512,11 @@ package android { field public static final int fastScrollTextColor = 16843609; // 0x1010359 field public static final int fastScrollThumbDrawable = 16843574; // 0x1010336 field public static final int fastScrollTrackDrawable = 16843577; // 0x1010339 + field public static final int fill = 16843781; // 0x1010405 field public static final int fillAfter = 16843197; // 0x10101bd field public static final int fillBefore = 16843196; // 0x10101bc field public static final int fillEnabled = 16843343; // 0x101024f + field public static final int fillOpacity = 16843780; // 0x1010404 field public static final int fillViewport = 16843130; // 0x101017a field public static final int filter = 16843035; // 0x101011b field public static final int filterTouchesWhenObscured = 16843460; // 0x10102c4 @@ -715,6 +720,7 @@ package android { field public static final int layout_x = 16843135; // 0x101017f field public static final int layout_y = 16843136; // 0x1010180 field public static final int left = 16843181; // 0x10101ad + field public static final int limitTo = 16843795; // 0x1010413 field public static final int lineSpacingExtra = 16843287; // 0x1010217 field public static final int lineSpacingMultiplier = 16843288; // 0x1010218 field public static final int lines = 16843092; // 0x1010154 @@ -817,6 +823,7 @@ package android { field public static final int parentActivityName = 16843687; // 0x10103a7 field public static final deprecated int password = 16843100; // 0x101015c field public static final int path = 16842794; // 0x101002a + field public static final int pathData = 16843782; // 0x1010406 field public static final int pathPattern = 16842796; // 0x101002c field public static final int pathPrefix = 16842795; // 0x101002b field public static final int permission = 16842758; // 0x1010006 @@ -826,7 +833,7 @@ package android { field public static final int persistent = 16842765; // 0x101000d field public static final int persistentDrawingCache = 16842990; // 0x10100ee field public static final deprecated int phoneNumber = 16843111; // 0x1010167 - field public static final int pinned = 16843778; // 0x1010402 + field public static final int pinned = 16843797; // 0x1010415 field public static final int pivotX = 16843189; // 0x10101b5 field public static final int pivotY = 16843190; // 0x10101b6 field public static final int popupAnimationStyle = 16843465; // 0x10102c9 @@ -881,6 +888,7 @@ package android { field public static final int readPermission = 16842759; // 0x1010007 field public static final int repeatCount = 16843199; // 0x10101bf field public static final int repeatMode = 16843200; // 0x10101c0 + field public static final int repeatStyle = 16843788; // 0x101040c field public static final int reqFiveWayNav = 16843314; // 0x1010232 field public static final int reqHardKeyboard = 16843305; // 0x1010229 field public static final int reqKeyboardType = 16843304; // 0x1010228 @@ -890,7 +898,7 @@ package android { field public static final int required = 16843406; // 0x101028e field public static final int requiredAccountType = 16843734; // 0x10103d6 field public static final int requiredForAllUsers = 16843728; // 0x10103d0 - field public static final int requiredForProfile = 16843777; // 0x1010401 + field public static final int requiredForProfile = 16843796; // 0x1010414 field public static final int requiresFadingEdge = 16843685; // 0x10103a5 field public static final int requiresSmallestWidthDp = 16843620; // 0x1010364 field public static final int resizeMode = 16843619; // 0x1010363 @@ -954,6 +962,7 @@ package android { field public static final int selectableItemBackground = 16843534; // 0x101030e field public static final int selectedDateVerticalBar = 16843591; // 0x1010347 field public static final int selectedWeekBackgroundColor = 16843586; // 0x1010342 + field public static final int sequence = 16843787; // 0x101040b field public static final int settingsActivity = 16843301; // 0x1010225 field public static final int shadowColor = 16843105; // 0x1010161 field public static final int shadowDx = 16843106; // 0x1010162 @@ -1025,6 +1034,10 @@ package android { field public static final int streamType = 16843273; // 0x1010209 field public static final int stretchColumns = 16843081; // 0x1010149 field public static final int stretchMode = 16843030; // 0x1010116 + field public static final int stroke = 16843783; // 0x1010407 + field public static final int strokeLineCap = 16843792; // 0x1010410 + field public static final int strokeOpacity = 16843784; // 0x1010408 + field public static final int strokeWidth = 16843785; // 0x1010409 field public static final int subtitle = 16843473; // 0x10102d1 field public static final int subtitleTextStyle = 16843513; // 0x10102f9 field public static final int subtypeExtraValue = 16843674; // 0x101039a @@ -1165,6 +1178,10 @@ package android { field public static final int translationX = 16843554; // 0x1010322 field public static final int translationY = 16843555; // 0x1010323 field public static final int translationZ = 16843769; // 0x10103f9 + field public static final int trigger = 16843777; // 0x1010401 + field public static final int trimPathEnd = 16843790; // 0x101040e + field public static final int trimPathOffset = 16843791; // 0x101040f + field public static final int trimPathStart = 16843789; // 0x101040d field public static final int type = 16843169; // 0x10101a1 field public static final int typeface = 16842902; // 0x1010096 field public static final int uiOptions = 16843672; // 0x1010398 @@ -1189,6 +1206,8 @@ package android { field public static final int verticalGap = 16843328; // 0x1010240 field public static final int verticalScrollbarPosition = 16843572; // 0x1010334 field public static final int verticalSpacing = 16843029; // 0x1010115 + field public static final int viewportHeight = 16843779; // 0x1010403 + field public static final int viewportWidth = 16843778; // 0x1010402 field public static final int visibility = 16842972; // 0x10100dc field public static final int visible = 16843156; // 0x1010194 field public static final int vmSafeMode = 16843448; // 0x10102b8 @@ -10845,6 +10864,27 @@ package android.graphics.drawable { method public void startTransition(int); } + public class VectorDrawable extends android.graphics.drawable.Drawable { + ctor public VectorDrawable(); + method public void draw(android.graphics.Canvas); + method public float geAnimationFraction(); + method public int getOpacity(); + method public int getRepeatCount(); + method public void setAlpha(int); + method public void setAnimationFraction(float); + method public void setColorFilter(android.graphics.ColorFilter); + method public void setDuration(long); + method public void setIntrinsicHeight(int); + method public void setIntrinsicWidth(int); + method public void setPadding(android.graphics.Rect); + method public void setPadding(int, int, int, int); + method public void setRepeatCount(int); + method public void setRepeatMode(int); + method public void start(); + method public void stop(); + field public static final int INFINITE = -1; // 0xffffffff + } + } package android.graphics.drawable.shapes { diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 4d272ee..eaaf0d9 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -4531,6 +4531,145 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + [] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 64cdc05..d96bbc1 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -2113,6 +2113,25 @@ + + + + + + + + + + + + + + + + + + + diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java index de2b68f..737970b 100644 --- a/graphics/java/android/graphics/drawable/Drawable.java +++ b/graphics/java/android/graphics/drawable/Drawable.java @@ -1062,6 +1062,8 @@ public abstract class Drawable { drawable = new ColorDrawable(); } else if (name.equals("shape")) { drawable = new GradientDrawable(); + } else if (name.equals("vector")) { + drawable = new VectorDrawable(); } else if (name.equals("scale")) { drawable = new ScaleDrawable(); } else if (name.equals("clip")) { diff --git a/graphics/java/android/graphics/drawable/VectorDrawable.java b/graphics/java/android/graphics/drawable/VectorDrawable.java new file mode 100644 index 0000000..f3dd2fc --- /dev/null +++ b/graphics/java/android/graphics/drawable/VectorDrawable.java @@ -0,0 +1,1955 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package android.graphics.drawable; + +import android.animation.ArgbEvaluator; +import android.animation.ObjectAnimator; +import android.animation.ValueAnimator; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.content.res.Resources.Theme; +import android.graphics.Canvas; +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.Rect; +import android.graphics.RectF; +import android.graphics.Region; +import android.location.Address; +import android.net.ParseException; +import android.sax.StartElementListener; +import android.util.AttributeSet; +import android.util.Log; +import android.util.Xml; +import android.view.animation.AccelerateDecelerateInterpolator; +import android.view.animation.Interpolator; +import android.view.animation.LinearInterpolator; + +import com.android.internal.R; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlPullParserFactory; + +import java.io.IOException; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +/** + * This lets you create a drawable based on an XML vector graphic + * It can be defined in an XML file with the <vector> element. + *

+ * The vector drawable has 6 elements: + *

+ *

+ *
<vector>
+ * The attribute android:trigger which defines a state change that + * will drive the animation
+ *
<size>
+ *
Used to defined the intrinsic Width Height size of the drawable using + * android:width and android:height
+ *
<viewport>
+ *
Used to defined the size of the virtual canvas the paths are drawn on. + * The size is defined using the attributes android:viewportHeight + * android:viewportWidth
+ *
<group>
+ *
Defines a "key frame" in the animation if there is only one group the + * drawable is static 2D image that has no animation.
+ *
<path>
+ *
Defines paths to be drawn. The path elements must be within a group + *
+ *
android:name + *
Defines the name of the path.
+ *
android:pathData + *
Defines path string.
+ *
android:fill + *
Defines the color to fill the path (none if not present).
+ *
android:stroke + *
Defines the color to draw the path outline (none if not present).
+ *
android:strokeWidth + *
The width a path stroke
+ *
android:strokeOpacity + *
The opacity of a path stroke
+ *
android:rotation + *
The amount to rotation the path stroke.
+ *
android:pivotX + *
The X coordinate of the center of rotation of a path
+ *
android:pivotY + *
The Y coordinate of the center of rotation of a path
+ *
android:fillOpacity + *
The opacity to fill the path with
+ *
android:trimPathStart + *
The fraction of the path to trim from the start from 0 to 1
+ *
android:trimPathEnd + *
The fraction of the path to trim from the end from 0 to 1
+ *
android:trimPathOffset + *
Shift trim region (allows showed region to include the start and end) from 0 to 1
+ *
android:clipToPath + *
Path will set the clip path
+ *
android:strokeLineCap + *
Sets the linecap for a stroked path: butt, round, square
+ *
android:strokeLineJoin + *
Sets the lineJoin for a stroked path: miter,round,bevel
+ *
android:strokeMiterLimit + *
Sets the Miter limit for a stroked path
+ *
android:state_pressed + *
Sets a condition to be met to draw path
+ *
android:state_focused + *
Sets a condition to be met to draw path
+ *
android:state_selected + *
Sets a condition to be met to draw path
+ *
android:state_window_focused + *
Sets a condition to be met to draw path
+ *
android:state_enabled + *
Sets a condition to be met to draw path
+ *
android:state_activated + *
Sets a condition to be met to draw path
+ *
android:state_accelerated + *
Sets a condition to be met to draw path
+ *
android:state_hovered + *
Sets a condition to be met to draw path
+ *
android:state_checked + *
Sets a condition to be met to draw path
+ *
android:state_checkable + *
Sets a condition to be met to draw path
+ *
+ *
+ *
<animation>
+ *
Used to customize the transition between two paths + *
+ *
android:sequence + *
Configures this animation sequence between the named paths.
+ *
android:limitTo + *
Limits an animation to only interpolate the selected variable + * unlimited, path, rotation, trimPathStart, trimPathEnd, trimPathOffset
+ *
android:repeatCount + *
Number of times to loop this aspect of the animation
+ *
android:durations + *
The duration of each step in the animation in milliseconds + * Must contain the number of named paths - 1
+ *
android:startDelay + *
+ *
android:repeatStyle + *
when repeating how does it repeat back and forth or a to b: forward, inAndOut
+ *
android:animate + *
linear, accelerate, decelerate, easing
+ *
+ *
+ */ +public class VectorDrawable extends Drawable { + private static final String LOGTAG = "VectorDrawable"; + public static final int INFINITE = ValueAnimator.INFINITE; + private static final String SHAPE_SIZE = "size"; + private static final String SHAPE_VIEWPORT = "viewport"; + private static final String SHAPE_GROUP = "group"; + private static final String SHAPE_PATH = "path"; + private static final String SHAPE_TRANSITION = "transition"; + private static final String SHAPE_ANIMATION = "animation"; + 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 int DEFAULT_DURATION = 1000; + private static final long DEFAULT_INFINITE_DURATION = 60 * 60 * 1000; + private VectorDrawableState mVectorState; + private int mAlpha = 0xFF; + + public VectorDrawable() { + mVectorState = new VectorDrawableState(); + mVectorState.mBasicAnimator = ObjectAnimator.ofFloat(this, "AnimationFraction", 0, 1); + setDuration(DEFAULT_DURATION); + } + + private VectorDrawable(VectorDrawableState state) { + mVectorState = new VectorDrawableState(state); + mVectorState.mBasicAnimator = ObjectAnimator.ofFloat(this, "AnimationFraction", 0, 1); + long duration = mVectorState.mVAnimatedPath.getTotalAnimationDuration(); + if (duration == -1) { // if it set to infinite set to 1 hour + duration = DEFAULT_INFINITE_DURATION; // TODO define correct approach for infinite + mVectorState.mBasicAnimator.setFloatValues(0, duration / 1000); + mVectorState.mBasicAnimator.setInterpolator(new LinearInterpolator()); + } + setDuration(duration); + } + + final static class VectorDrawableState extends ConstantState { + int mChangingConfigurations; + ValueAnimator mBasicAnimator; + VAnimatedPath mVAnimatedPath = new VAnimatedPath(); + Rect mPadding; + int mIntrinsicHeight; + int mIntrinsicWidth; + + public VectorDrawableState(){ + } + + public VectorDrawableState(VectorDrawableState copy){ + mChangingConfigurations = copy.mChangingConfigurations; + mVAnimatedPath = new VAnimatedPath(copy.mVAnimatedPath); + mPadding = new Rect(copy.mPadding); + mIntrinsicHeight = copy.mIntrinsicHeight; + mIntrinsicWidth = copy.mIntrinsicWidth; + } + + @Override + public Drawable newDrawable() { + return new VectorDrawable(this); + } + + @Override + public int getChangingConfigurations() { + return mChangingConfigurations; + } + } + + /* (non-Javadoc) + * @see android.graphics.drawable.Drawable#getConstantState() + */ + @Override + public ConstantState getConstantState() { + return mVectorState; + } + + /** + * start the animation + */ + public void start() { + mVectorState.mBasicAnimator.start(); + } + + /** + * Stop the animation. + */ + public void stop() { + mVectorState.mBasicAnimator.end(); + } + + /** + * Get the current time point in the animation + * + * @return the current point on the animation + */ + public float geAnimationFraction() { + return mVectorState.mVAnimatedPath.getValue(); + } + + /** + * set the time point in the animation + * + * @param value the point along the animation typically between 0 and 1 + */ + public void setAnimationFraction(float value) { + mVectorState.mVAnimatedPath.setAnimationFraction(value); + invalidateSelf(); + } + + /** + * set the amount of time the animation will take + * + * @param duration amount of time in milliseconds + */ + public void setDuration(long duration) { + mVectorState.mBasicAnimator.setDuration(duration); + } + + /** + * Defines what this animation should do when it reaches the end. This setting is applied only + * when the repeat count is either greater than 0 or {@link #INFINITE}. + */ + public void setRepeatMode(int mode) { + mVectorState.mBasicAnimator.setRepeatMode(mode); + } + + /** + * Sets animation to repeat + * + * @param repeat True if this drawable repeats its animation + */ + public void setRepeatCount(int repeat) { + mVectorState.mBasicAnimator.setRepeatCount(repeat); + } + + /** + * @return True if this drawable repeats its animation + */ + public int getRepeatCount() { + return mVectorState.mBasicAnimator.getRepeatCount(); + } + + @Override + public boolean isStateful() { + return true; + } + + @Override + protected boolean onStateChange(int[] state) { + mVectorState.mVAnimatedPath.setState(state); + int direction = mVectorState.mVAnimatedPath.getTrigger(state); + if (direction>0) { + animateForward(); + } else if (direction<0) { + animateBackward(); + } + super.onStateChange(state); + invalidateSelf(); + return true; + } + + private void animateForward(){ + if (!mVectorState.mBasicAnimator.isStarted()) { + mVectorState.mBasicAnimator.setFloatValues(0,1); + start(); + } + } + + private void animateBackward(){ + if (!mVectorState.mBasicAnimator.isStarted()) { + mVectorState.mBasicAnimator.setFloatValues(.99f,0); + start(); + } + } + + @Override + public void draw(Canvas canvas) { + mVectorState.mVAnimatedPath.draw(canvas); + } + + @Override + public void setAlpha(int alpha) { + // TODO correct handling of transparent + if (mAlpha != alpha) { + mAlpha = alpha; + invalidateSelf(); + } + } + + /** + * Not implemented yet + * @hide + */ + @Override + public void setColorFilter(ColorFilter colorFilter) { + // TODO: support color filter + } + + /** + * Returns a {@link android.graphics.PixelFormat graphics.PixelFormat} + * value of TRANSLUCENT. + */ + @Override + public int getOpacity() { + return PixelFormat.TRANSLUCENT; + } + + /** + * Sets padding for this shape, defined by a Rect object. Define the padding in the Rect object + * as: left, top, right, bottom. + */ + public void setPadding(Rect padding) { + setPadding(padding.left, padding.top, padding.right, padding.bottom); + } + + /** + * Sets padding for the shape. + * + * @param left padding for the left side (in pixels) + * @param top padding for the top (in pixels) + * @param right padding for the right side (in pixels) + * @param bottom padding for the bottom (in pixels) + */ + public void setPadding(int left, int top, int right, int bottom) { + if ((left | top | right | bottom) == 0) { + mVectorState.mPadding = null; + } else { + if (mVectorState.mPadding == null) { + mVectorState.mPadding = new Rect(); + } + mVectorState.mPadding.set(left, top, right, bottom); + } + invalidateSelf(); + } + + /** + * Sets the intrinsic (default) width for this shape. + * + * @param width the intrinsic width (in pixels) + */ + public void setIntrinsicWidth(int width) { + if (mVectorState.mIntrinsicWidth != width) { + mVectorState.mIntrinsicWidth = width; + invalidateSelf(); + } + } + + /** + * Sets the intrinsic (default) height for this shape. + * + * @param height the intrinsic height (in pixels) + */ + public void setIntrinsicHeight(int height) { + if (mVectorState.mIntrinsicHeight != height) { + mVectorState.mIntrinsicHeight = height; + invalidateSelf(); + } + } + + @Override + public int getIntrinsicWidth() { + return mVectorState.mIntrinsicWidth; + } + + @Override + public int getIntrinsicHeight() { + return mVectorState.mIntrinsicHeight; + } + + @Override + public boolean getPadding(Rect padding) { + if (mVectorState.mPadding != null) { + padding.set(mVectorState.mPadding); + return true; + } else { + return super.getPadding(padding); + } + } + + /** @hide */ + public static VectorDrawable create(Resources resources, int rid) { + try { + VectorDrawable drawable = new VectorDrawable(); + XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); + factory.setNamespaceAware(true); + XmlPullParser xpp = resources.getXml(rid); + AttributeSet attrs = Xml.asAttributeSet(xpp); + drawable.inflate(resources, xpp, attrs); + drawable.setAnimationFraction(0); + return drawable; + } catch (XmlPullParserException e) { + Log.e(LOGTAG, "parser error", e); + } catch (IOException e) { + Log.e(LOGTAG, "parser error", e); + } + return null; + } + + @Override + public void inflate(Resources res, XmlPullParser parser, AttributeSet attrs, Theme theme) + throws XmlPullParserException, IOException { + setAnimatedPath(inflateInternal(res, parser, attrs)); + } + + private VAnimatedPath inflateInternal(Resources res, XmlPullParser parser, AttributeSet attrs) + throws XmlPullParserException, IOException { + boolean noSizeTag = true; + boolean noViewportTag = true; + boolean noGroupTag = true; + boolean noPathTag = true; + final VAnimatedPath animatedPath = new VAnimatedPath(); + VectorDrawable.VGroup currentGroup = null; + + int eventType = parser.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tagName = parser.getName(); + if (SHAPE_PATH.equals(tagName)) { + VectorDrawable.VPath p = new VPath(res, attrs); + currentGroup.add(p); + noPathTag = false; + } else if (SHAPE_ANIMATION.equals(tagName)) { + VectorDrawable.VAnimation anim = + new VAnimation(res, attrs, animatedPath.mGroupList); + animatedPath.addAnimation(anim); + } else if (SHAPE_SIZE.equals(tagName)) { + animatedPath.parseSize(res, attrs); + noSizeTag = false; + } else if (SHAPE_VIEWPORT.equals(tagName)) { + animatedPath.parseViewport(res, attrs); + noViewportTag = false; + } else if (SHAPE_GROUP.equals(tagName)) { + currentGroup = new VectorDrawable.VGroup(); + animatedPath.mGroupList.add(currentGroup); + noGroupTag = false; + } else if (SHAPE_VECTOR.equals(tagName)) { + TypedArray a = res.obtainAttributes(attrs, R.styleable.VectorDrawable); + animatedPath.setTrigger(a.getInteger(R.styleable.VectorDrawable_trigger, 0)); + a.recycle(); + } + } + eventType = parser.next(); + } + if (noSizeTag || noViewportTag || noGroupTag || noPathTag) { + StringBuffer tag = new StringBuffer(); + if (noSizeTag) { + tag.append("size"); + } + if (noViewportTag){ + if (tag.length()>0) { + tag.append(" & "); + } + tag.append("size"); + } + if (noGroupTag){ + if (tag.length()>0) { + tag.append(" & "); + } + tag.append("group"); + } + if (noPathTag){ + if (tag.length()>0) { + tag.append(" or "); + } + tag.append("path"); + } + throw new XmlPullParserException("no "+tag+" defined"); + } + // post parse cleanup + animatedPath.parseFinish(); + return animatedPath; + } + + private void setAnimatedPath(VAnimatedPath animatedPath) { + mVectorState.mVAnimatedPath = animatedPath; + setIntrinsicWidth((int) mVectorState.mVAnimatedPath.mBaseWidth); + setIntrinsicHeight((int) mVectorState.mVAnimatedPath.mBaseHeight); + long duration = mVectorState.mVAnimatedPath.getTotalAnimationDuration(); + if (duration == -1) { // if it set to infinite set to 1 hour + duration = DEFAULT_INFINITE_DURATION; // TODO define correct approach for infinite + mVectorState.mBasicAnimator.setFloatValues(0, duration / 1000); + mVectorState.mBasicAnimator.setInterpolator(new LinearInterpolator()); + } + setDuration(duration); + + setAnimationFraction(0); + } + + @Override + public boolean setVisible(boolean visible, boolean restart) { + boolean changed = super.setVisible(visible, restart); + if (visible) { + if (changed || restart) { + setAnimationFraction(0); + } + } else { + stop(); + } + return changed; + } + + private static class VAnimatedPath { + private static final String LOGTAG = "VAnimatedPath"; + + private ArrayList mCurrentAnimList = null; + private VPath[] mCurrentPaths; + private float mAnimationValue = 0; // value goes from 0 to 1 + private Paint mStrokePaint = null; + private Paint mFillPaint = null; + private PathMeasure mPathMeasure; + private Path mPath = new Path(); + private Path mRenderPath = new Path(); + private Matrix mMatrix = new Matrix(); + private long mTotalDuration; + private int[] mCurrentState = new int[0]; + private int mTrigger; + private boolean mTriggerState; + ArrayList mGroupList = new ArrayList(); + float mBaseWidth = 1; + float mBaseHeight = 1; + float mViewportWidth; + float mViewportHeight; + + public VAnimatedPath() { + setup(); + } + public VAnimatedPath(VAnimatedPath copy) { + setup(); + mCurrentAnimList = new ArrayList(copy.mCurrentAnimList); + mGroupList = new ArrayList(copy.mGroupList); + if (copy.mCurrentPaths != null) { + mCurrentPaths = new VPath[copy.mCurrentPaths.length]; + for (int i = 0; i < mCurrentPaths.length; i++) { + mCurrentPaths[i] = new VPath(copy.mCurrentPaths[i]); + } + } + mAnimationValue = copy.mAnimationValue; // time goes from 0 to 1 + + mBaseWidth = copy.mBaseWidth; + mBaseHeight = copy.mBaseHeight; + mViewportWidth = copy.mViewportHeight; + mViewportHeight = copy.mViewportHeight; + mTotalDuration = copy.mTotalDuration; + mTrigger = copy.mTrigger; + mCurrentState = new int[0]; + } + + public void setTrigger(int trigger){ + int []lut = { 0, + R.attr.state_pressed, + R.attr.state_focused, + R.attr.state_hovered, + R.attr.state_selected, + R.attr.state_checkable, + R.attr.state_checked, + R.attr.state_activated, + R.attr.state_focused + }; + mTrigger = lut[trigger]; + } + + private void setup(){ + mStrokePaint = new Paint(); + mStrokePaint.setStyle(Paint.Style.STROKE); + mStrokePaint.setAntiAlias(true); + mFillPaint = new Paint(); + mFillPaint.setStyle(Paint.Style.FILL); + mFillPaint.setAntiAlias(true); + } + + public long getTotalAnimationDuration() { + mTotalDuration = 0; + int size = mCurrentAnimList.size(); + for (int i = 0; i < size; i++) { + VAnimation vAnimation = mCurrentAnimList.get(i); + long t = vAnimation.getTotalDuration(); + if (t == -1) { + mTotalDuration = -1; + return -1; + } + mTotalDuration = Math.max(mTotalDuration, t); + } + + return mTotalDuration; + } + + public float getValue() { + return mAnimationValue; + } + + /** + * @param value the point along the animations to show typically between 0.0f and 1.0f + * @return true if you need to keep repeating + */ + public boolean setAnimationFraction(float value) { + int len = mCurrentPaths.length; + getTotalAnimationDuration(); + long animationTime = (long) (value * mTotalDuration); + + for (int i = 0; i < len; i++) { + VPath path = mCurrentPaths[i]; + animationTime = + (long) ((mTotalDuration == -1) ? value * 1000 : mTotalDuration * value); + int size = mCurrentAnimList.size(); + for (int j = 0; j < size; j++) { + VAnimation vAnimation = mCurrentAnimList.get(j); + if (vAnimation.doesAdjustPath(path)) { + mCurrentPaths[i] = vAnimation.getPathAtTime(animationTime, path); + } + } + } + this.mAnimationValue = value; + if (mTotalDuration == -1) { + return true; + } else { + return animationTime < mTotalDuration; + } + } + + public void draw(Canvas canvas) { + int w = canvas.getWidth(); + int h = canvas.getHeight(); + float scale = w / mViewportWidth; + scale = Math.min(h / mViewportHeight, scale); + + if (mCurrentPaths == null) { + Log.e(LOGTAG,"mCurrentPaths == null"); + return; + } + + for (int i = 0; i < mCurrentPaths.length; i++) { + if (mCurrentPaths[i] != null && mCurrentPaths[i].isVisible(mCurrentState)) { + drawPath(mCurrentPaths[i], canvas, w, h); + } + } + } + + private void drawPath(VPath vPath, Canvas canvas, int w, int h) { + float scale = w / mViewportWidth; + scale = Math.min(h / mViewportHeight, scale); + + vPath.toPath(mPath); + Path path = mPath; + + if (vPath.mTrimPathStart != 0.0f || vPath.mTrimPathEnd != 1.0f) { + float start = (vPath.mTrimPathStart + vPath.mTrimPathOffset) % 1.0f; + float end = (vPath.mTrimPathEnd + vPath.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.reset(); + mMatrix.reset(); + + mMatrix.postRotate(vPath.mRotate, vPath.mPivotX, vPath.mPivotY); + mMatrix.postScale(scale, scale, mViewportWidth / 2f, mViewportHeight / 2f); + mMatrix.postTranslate(w / 2f - mViewportWidth / 2f, h / 2f - mViewportHeight / 2f); + + mRenderPath.addPath(path, mMatrix); + + if (vPath.mClip) { + canvas.clipPath(mRenderPath, Region.Op.REPLACE); + } + if (vPath.mFillColor != 0) { + mFillPaint.setColor(vPath.mFillColor); + int alpha = 0xFF & (vPath.mFillColor >> 24); + mFillPaint.setAlpha(alpha); + canvas.drawPath(mRenderPath, mFillPaint); + } + if (vPath.mStrokeColor != 0) { + if (vPath.mStrokelineJoin != null) { + mStrokePaint.setStrokeJoin(vPath.mStrokelineJoin); + } + if (vPath.mStrokelineCap != null) { + mStrokePaint.setStrokeCap(vPath.mStrokelineCap); + } + mStrokePaint.setStrokeMiter(vPath.mStrokeMiterlimit * scale); + mStrokePaint.setColor(vPath.mStrokeColor); + mStrokePaint.setAlpha(0xFF & (vPath.mStrokeColor >> 24)); + mStrokePaint.setStrokeWidth(vPath.mStrokeWidth * scale); + canvas.drawPath(mRenderPath, mStrokePaint); + } + } + + /** + * Ensure there is at least one animation for every path in group (linking them by names) + * Build the "current" path based on the first group + * TODO: improve memory use & performance or move to C++ + */ + public void parseFinish() { + HashMap newAnimations = new HashMap(); + for (VGroup group : mGroupList) { + for (VPath vPath : group.getPaths()) { + if (!vPath.mAnimated) { + VAnimation ap = null; + + if (!newAnimations.containsKey(vPath.getID())) { + newAnimations.put(vPath.getID(), ap = new VAnimation()); + } else { + ap = newAnimations.get(vPath.getID()); + } + ap.addPath(vPath); + vPath.mAnimated = true; + } + } + } + if (mCurrentAnimList == null) { + mCurrentAnimList = new ArrayList(); + } + mCurrentAnimList.addAll(newAnimations.values()); + Collection paths = mGroupList.get(0).getPaths(); + mCurrentPaths = paths.toArray(new VPath[paths.size()]); + for (int i = 0; i < mCurrentPaths.length; i++) { + mCurrentPaths[i] = new VPath(mCurrentPaths[i]); + } + } + + public void setState(int[] state) { + mCurrentState = Arrays.copyOf(state, state.length); + } + + int getTrigger(int []state){ + if (mTrigger == 0) return 0; + for (int i = 0; i < state.length; i++) { + if (state[i] == mTrigger){ + if (mTriggerState) + return 0; + mTriggerState = true; + return 1; + } + } + if (mTriggerState) { + mTriggerState = false; + return -1; + } + return 0; + } + + public void addAnimation(VAnimation anim) { + if (mCurrentAnimList == null) { + mCurrentAnimList = new ArrayList(); + } + mCurrentAnimList.add(anim); + } + + private void parseViewport(Resources r, AttributeSet attrs) + throws XmlPullParserException { + TypedArray a = r.obtainAttributes(attrs, R.styleable.VectorDrawableViewport); + mViewportWidth = a.getFloat(R.styleable.VectorDrawableViewport_viewportWidth, 0); + mViewportHeight = a.getFloat(R.styleable.VectorDrawableViewport_viewportHeight, 0); + if (mViewportWidth == 0 || mViewportHeight == 0) { + throw new XmlPullParserException(a.getPositionDescription()+ + " tag requires viewportWidth & viewportHeight to be set"); + } + a.recycle(); + } + + private void parseSize(Resources r, AttributeSet attrs) + throws XmlPullParserException { + TypedArray a = r.obtainAttributes(attrs, R.styleable.VectorDrawableSize); + mBaseWidth = a.getDimension(R.styleable.VectorDrawableSize_width, 0); + mBaseHeight = a.getDimension(R.styleable.VectorDrawableSize_height, 0); + if (mBaseWidth == 0 || mBaseHeight == 0) { + throw new XmlPullParserException(a.getPositionDescription()+ + " tag requires width & height to be set"); + } + a.recycle(); + } + } + + private static class VAnimation { + private static final String LOGTAG = "VAnimation"; + private VPath[] mPaths = new VPath[0]; + + public enum Style { + INTERPOLATE, CROSSFADE, WIPE + } + Interpolator mAnimInterpolator = new AccelerateDecelerateInterpolator(); + private Style mStyle; + private int mLimitProperty = 0; + private long[] mDuration = {DEFAULT_DURATION}; + private long mStartOffset; + private long mRepeat = 1; + private HashSetmSeqMap = new HashSet(); + private long mWipeDirection; + private int mMode = 0; // forward = 0 inAndOut = 1; + private int mInterpolatorType; + private String mId; + private final static int DIRECTION_FORWARD = 0; + private final static int DIRECTION_IN_AND_OUT = 1; + + public VAnimation() { + } + + public boolean doesAdjustPath(VPath path) { + return mSeqMap.contains(path.getID()); + } + + public VAnimation(Resources r, AttributeSet attrs, ArrayList groups) + throws XmlPullParserException { + String value; + String[] sp; + int name; + + TypedArray a = r.obtainAttributes(attrs, R.styleable.VectorDrawableAnimation); + + value = a.getString(R.styleable.VectorDrawableAnimation_sequence); + if (value != null) { + sp = value.split(","); + VectorDrawable.VPath[] paths = new VectorDrawable.VPath[sp.length]; + + for (int j = 0; j < sp.length; j++) { + mSeqMap.add(sp[j].trim()); + VectorDrawable.VPath path = groups.get(j).get(sp[j]); + path.mAnimated = true; + paths[j] = path; + } + setPaths(paths); + } + + setLimitProperty(a.getInt(R.styleable.VectorDrawableAnimation_limitTo, 0)); + + name = R.styleable.VectorDrawableAnimation_durations; + value = a.getString(name); + if (value != null) { + long totalDuration = 0; + sp = value.split(","); + long[] dur = new long[sp.length]; + for (int j = 0; j < dur.length; j++) { + dur[j] = Long.parseLong(sp[j]); + totalDuration += dur[j]; + } + if (totalDuration == 0){ + throw new XmlPullParserException(a.getPositionDescription()+ + "total duration must not be zero"); + } + setDuration(dur); + } + + setRepeat(a.getInt(R.styleable.VectorDrawableAnimation_repeatCount, 1)); + + setStartOffset(a.getInt(R.styleable.VectorDrawableAnimation_startDelay, 0)); + + setMode(a.getInt(R.styleable.VectorDrawableAnimation_repeatStyle, 0)); + + fixMissingParameters(); + a.recycle(); + } + + public String getId() { + if (mId == null) { + mId = mPaths[0].getID(); + for (int i = 1; i < mPaths.length; i++) { + mId += mPaths[i].getID(); + } + } + return mId; + } + + public String getPathName() { + return mPaths[0].getID(); + } + + public Style getStyle() { + return mStyle; + } + + public void setStyle(Style style) { + this.mStyle = style; + } + + public int getLimitProperty() { + return mLimitProperty; + } + + public void setLimitProperty(int limitProperty) { + this.mLimitProperty = limitProperty; + } + + public long[] getDuration() { + return mDuration; + } + + public void setDuration(long[] duration) { + this.mDuration = duration; + } + + public long getRepeat() { + return mRepeat; + } + + public void setRepeat(long repeat) { + this.mRepeat = repeat; + } + + public long getStartOffset() { + return mStartOffset; + } + + public void setStartOffset(long startOffset) { + this.mStartOffset = startOffset; + } + + public long getWipeDirection() { + return mWipeDirection; + } + + public void setWipeDirection(long wipeDirection) { + this.mWipeDirection = wipeDirection; + } + + public int getMode() { + return mMode; + } + + public void setMode(int mode) { + this.mMode = mode; + } + + public int getInterpolator() { + return mInterpolatorType; + } + + public void setInterpolator(int interpolator) { + this.mInterpolatorType = interpolator; + } + + /** + * compute the total time in milliseconds + * + * @return the total time in milliseconds the animation will take + */ + public long getTotalDuration() { + long total = mStartOffset; + if (getRepeat() == -1) { + return -1; + } + for (int i = 0; i < mDuration.length; i++) { + if (mRepeat > 1) { + total += mDuration[i] * mRepeat; + } else { + total += mDuration[i]; + } + } + return total; + } + + public void setPaths(VPath[] paths) { + this.mPaths = paths; + } + + public void addPath(VPath path) { + mPaths = Arrays.copyOf(mPaths, mPaths.length + 1); + mPaths[mPaths.length - 1] = path; + } + + public boolean containsPath(String pathid) { + for (int i = 0; i < mPaths.length; i++) { + if (mPaths[i].getID().equals(pathid)) { + return true; + } + } + return false; + } + + public void interpolate(VPath p1, VPath p2, float time, VPath dest) { + dest.interpolate(time, p1, p2, dest, mLimitProperty); + } + + public VPath getPathAtTime(long milliseconds, VPath dest) { + if (mPaths.length == 1) { + dest.copyFrom(mPaths[0]); + return dest; + } + long point = milliseconds - mStartOffset; + if (point < 0) { + point = 0; + } + float time = 0; + long sum = mDuration[0]; + for (int i = 1; i < mDuration.length; i++) { + sum += mDuration[i]; + } + + if (mRepeat > 1) { + time = point / (float) (sum * mRepeat); + time = mAnimInterpolator.getInterpolation(time); + + if (mMode == DIRECTION_IN_AND_OUT) { + point = ((long) (time * sum * 2 * mRepeat)) % (sum * 2); + if (point > sum) { + point = sum * 2 - point; + } + } else { + point = ((long) (time * sum * mRepeat)) % sum; + } + } else if (mRepeat == 1) { + time = point / (float) (sum * mRepeat); + time = mAnimInterpolator.getInterpolation(time); + if (mMode == DIRECTION_IN_AND_OUT) { + point = ((long) (time * sum * 2 * mRepeat)); + if (point > sum) { + point = sum * 2 - point; + } + } else { + point = Math.min(((long) (time * sum * mRepeat)), sum); + } + + } else { // repeat = -1 + if (mMode == DIRECTION_IN_AND_OUT) { + point = point % (sum * 2); + if (point > sum) { + point = sum * 2 - point; + } + time = point / (float) sum; + } else { + point = point % sum; + time = point / (float) sum; + } + } + + int transition = 0; + while (point > mDuration[transition]) { + point -= mDuration[transition++]; + } + if (mPaths.length > (transition + 1)) { + if (mPaths[transition].getID() != dest.getID()) { + dest.copyFrom(mPaths[transition]); + } + interpolate(mPaths[transition], mPaths[transition + 1], + point / (float) mDuration[transition], dest); + } else { + interpolate(mPaths[transition], mPaths[transition], 0, dest); + } + return dest; + } + + void fixMissingParameters() { + // fix missing points + float rotation = Float.NaN; + float rotationY = Float.NaN; + float rotationX = Float.NaN; + for (int i = 0; i < mPaths.length; i++) { + if (mPaths[i].mPivotX > 0) { + rotationX = mPaths[i].mPivotX; + } + if (mPaths[i].mPivotY > 0) { + rotationY = mPaths[i].mPivotY; + } + if (mPaths[i].mRotate > 0) { + rotation = mPaths[i].mRotate; + } + } + if (rotation > 0) { + for (int i = 0; i < mPaths.length; i++) { + if (mPaths[i].mPivotX == 0) { + mPaths[i].mPivotX = rotationX; + } + if (mPaths[i].mPivotY == 0) { + mPaths[i].mPivotY = rotationY; + } + } + } + } + } + + private static class VGroup { + private HashMap mVGPathMap = new HashMap(); + private ArrayList mVGList = new ArrayList(); + + public void add(VPath path) { + String id = path.getID(); + mVGPathMap.put(id, path); + mVGList.add(path); + } + + public VPath get(String name) { + return mVGPathMap.get(name); + } + + /** + * Must return in order of adding + * @return ordered list of paths + */ + public Collection getPaths() { + return mVGList; + } + + public int size() { + return mVGPathMap.size(); + } + } + + private static class VPath { + private static final int LIMIT_ALL = 0; + private static final int LIMIT_PATH = 1; + private static final int LIMIT_ROTATE = 2; + private static final int LIMIT_TRIM_PATH_START = 3; + private static final int LIMIT_TRIM_PATH_OFFSET = 5; + private static final int LIMIT_TRIM_PATH_END = 4; + private static final int STATE_UNDEFINED=0; + private static final int STATE_TRUE=1; + private static final int STATE_FALSE=2; + private static final int MAX_STATES = 10; + private VNode[] mNode = null; + private String mId; + int mStrokeColor = 0; + float mStrokeWidth = 0; + float mStrokeOpacity = Float.NaN; + int mFillColor = 0; + int mFillRule; + float mFillOpacity = Float.NaN; + float mRotate = 0; + float mPivotX = 0; + float mPivotY = 0; + float mTrimPathStart = 0; + float mTrimPathEnd = 1; + float mTrimPathOffset = 0; + boolean mAnimated = false; + boolean mClip = false; + public Paint.Cap mStrokelineCap = null; + public Paint.Join mStrokelineJoin = null; + float mStrokeMiterlimit = 4; + private int[] mCheckState = new int[MAX_STATES]; + private boolean[] mCheckValue = new boolean[MAX_STATES]; + private int mNumberOfStates = 0; + private int mNumberOfTrue = 0; + + public void addStateFilter(int state, boolean condition) { + int k = 0; + while (k < mNumberOfStates) { + if (mCheckState[mNumberOfStates] == state) + break; + k++; + } + mCheckState[k] = state; + mCheckValue[k] = condition; + if (k==mNumberOfStates){ + mNumberOfStates++; + } + if (condition) { + mNumberOfTrue++; + } + } + + int getState(int state){ + for (int i = 0; i < mNumberOfStates; i++) { + if (mCheckState[mNumberOfStates] == state){ + return (mCheckValue[i])?STATE_TRUE:STATE_FALSE; + } + } + return STATE_UNDEFINED; + } + /** + * @return the name of the path + */ + public String getName() { + return mId; + } + + public void toPath(Path path) { + path.reset(); + if (mNode != null) { + VNode.createPath(mNode, path); + } + } + + public VPath() { + mId = this.toString(); // to ensure paths have unique names + } + + public VPath(VPath p) { + copyFrom(p); + } + + public String getID(){ + return mId; + } + + public VPath(Resources r, AttributeSet attrs) { + TypedArray a = r.obtainAttributes(attrs, R.styleable.VectorDrawablePath); + mClip = a.getBoolean(R.styleable.VectorDrawablePath_clipToPath, false); + mId = a.getString(R.styleable.VectorDrawablePath_name); + mNode = parsePath(a.getString(R.styleable.VectorDrawablePath_pathData)); + mFillColor = a.getColor(R.styleable.VectorDrawablePath_fill, 0); + mFillOpacity = a.getFloat(R.styleable.VectorDrawablePath_fillOpacity, Float.NaN); + + if (!Float.isNaN(mFillOpacity)) { + mFillColor &= 0x00FFFFFF; + mFillColor |= ((int) (0xFF * mFillOpacity)) << 24; + } + mRotate = a.getFloat(R.styleable.VectorDrawablePath_rotation, 0); + mPivotX = a.getFloat(R.styleable.VectorDrawablePath_pivotX, 0); + mPivotY = a.getFloat(R.styleable.VectorDrawablePath_pivotY, 0); + int lineCap = a.getInt(R.styleable.VectorDrawablePath_strokeLineCap, 0); + switch (lineCap) { + case LINECAP_BUTT: + mStrokelineCap = Paint.Cap.BUTT; + break; + case LINECAP_ROUND: + mStrokelineCap = Paint.Cap.ROUND; + break; + case LINECAP_SQUARE: + mStrokelineCap = Paint.Cap.SQUARE; + break; + } + int lineJoin = a.getInt(R.styleable.VectorDrawablePath_strokeLineJoin, 0); + switch (lineJoin) { + case LINEJOIN_MITER: + mStrokelineJoin = Paint.Join.MITER; + break; + case LINEJOIN_ROUND: + mStrokelineJoin = Paint.Join.ROUND; + break; + case LINEJOIN_BEVEL: + mStrokelineJoin = Paint.Join.BEVEL; + break; + } + mStrokeMiterlimit = a.getFloat(R.styleable.VectorDrawablePath_strokeMiterLimit, + mStrokeMiterlimit); + mStrokeColor = a.getColor(R.styleable.VectorDrawablePath_stroke, mStrokeColor); + mStrokeOpacity = a.getFloat(R.styleable.VectorDrawablePath_strokeOpacity, Float.NaN); + if (!Float.isNaN(mStrokeOpacity)) { + mStrokeColor &= 0x00FFFFFF; + mStrokeColor |= ((int) (0xFF * mStrokeOpacity)) << 24; + } + mStrokeWidth = a.getFloat(R.styleable.VectorDrawablePath_strokeWidth, 0); + mTrimPathEnd = a.getFloat(R.styleable.VectorDrawablePath_trimPathEnd, 1); + mTrimPathOffset = a.getFloat(R.styleable.VectorDrawablePath_trimPathOffset, 0); + mTrimPathStart = a.getFloat(R.styleable.VectorDrawablePath_trimPathStart, 0); + int[] states = {R.styleable.VectorDrawablePath_state_activated, + R.styleable.VectorDrawablePath_state_checkable, + R.styleable.VectorDrawablePath_state_checked, + R.styleable.VectorDrawablePath_state_enabled, + R.styleable.VectorDrawablePath_state_focused, + R.styleable.VectorDrawablePath_state_hovered, + R.styleable.VectorDrawablePath_state_pressed, + R.styleable.VectorDrawablePath_state_selected, + R.styleable.VectorDrawablePath_state_window_focused}; + for (int state : states) { + if (a.hasValue(state)) { + addStateFilter(state, a.getBoolean(state, false)); + } + } + a.recycle(); + } + + 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 void addNode(ArrayList list, char cmd, float[] val) { + list.add(new VectorDrawable.VNode(cmd, val)); + } + + /** + * 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[] tmp = new float[s.length()]; + int count = 0; + int pos = 1, end; + while ((end = extract(s, pos)) >= 0) { + if (pos < end) { + tmp[count++] = Float.parseFloat(s.substring(pos, end)); + } + pos = end + 1; + } + // handle the final float if there is one + if (pos < s.length()) { + tmp[count++] = Float.parseFloat(s.substring(pos, s.length())); + } + return Arrays.copyOf(tmp, count); + } catch (NumberFormatException e){ + Log.e(LOGTAG,"error in parsing \""+s+"\""); + throw e; + } + } + + /** + * calculate the position of the next comma or space + * @param s the string to search + * @param start the position to start searching + * @return the position of the next comma or space or -1 if none found + */ + private static int extract(String s, int start) { + int space = s.indexOf(' ', start); + int comma = s.indexOf(',', start); + if (space == -1) { + return comma; + } + if (comma == -1) { + return space; + } + return (comma > space) ? space : comma; + } + + private VectorDrawable.VNode[] parsePath(String value) { + int start = 0; + int end = 1; + + ArrayList list = new ArrayList(); + while (end < value.length()) { + end = nextStart(value, end); + String s = value.substring(start, end); + float[] val = getFloats(s); + addNode(list, s.charAt(0), val); + + start = end; + end++; + } + if ((end - start) == 1 && start < value.length()) { + + addNode(list, value.charAt(start), new float[0]); + } + return list.toArray(new VectorDrawable.VNode[list.size()]); + } + + public void copyFrom(VPath p1) { + mNode = new VNode[p1.mNode.length]; + for (int i = 0; i < mNode.length; i++) { + mNode[i] = new VNode(p1.mNode[i]); + } + mId = p1.mId; + mStrokeColor = p1.mStrokeColor; + mFillColor = p1.mFillColor; + mStrokeWidth = p1.mStrokeWidth; + mRotate = p1.mRotate; + mPivotX = p1.mPivotX; + mPivotY = p1.mPivotY; + mAnimated = p1.mAnimated; + mTrimPathStart = p1.mTrimPathStart; + mTrimPathEnd = p1.mTrimPathEnd; + mTrimPathOffset = p1.mTrimPathOffset; + mStrokelineCap = p1.mStrokelineCap; + mStrokelineJoin = p1.mStrokelineJoin; + mStrokeMiterlimit = p1.mStrokeMiterlimit; + mNumberOfStates = p1.mNumberOfStates; + for (int i = 0; i < mNumberOfStates; i++) { + mCheckState[i] = p1.mCheckState[i]; + mCheckValue[i] = p1.mCheckValue[i]; + } + + mFillRule = p1.mFillRule; + } + + public static VPath interpolate(float t, VPath p1, VPath p2, VPath returnPath, int limit) { + if (limit == LIMIT_ALL || limit == LIMIT_PATH) { + if (returnPath.mNode == null || returnPath.mNode.length != p1.mNode.length) { + returnPath.mNode = new VNode[p1.mNode.length]; + } + for (int i = 0; i < returnPath.mNode.length; i++) { + if (returnPath.mNode[i] == null) { + returnPath.mNode[i] = new VNode(p1.mNode[i], p2.mNode[i], t); + } else { + returnPath.mNode[i].interpolate(p1.mNode[i], p2.mNode[i], t); + } + } + } + float t1 = 1 - t; + switch (limit) { + case LIMIT_ALL: + returnPath.mRotate = t1 * p1.mRotate + t * p2.mRotate; + returnPath.mPivotX = t1 * p1.mPivotX + t * p2.mPivotX; + returnPath.mPivotY = t1 * p1.mPivotY + t * p2.mPivotY; + returnPath.mClip = p1.mClip | p2.mClip; + + returnPath.mTrimPathStart = t1 * p1.mTrimPathStart + t * p2.mTrimPathStart; + returnPath.mTrimPathEnd = t1 * p1.mTrimPathEnd + t * p2.mTrimPathEnd; + returnPath.mTrimPathOffset = t1 * p1.mTrimPathOffset + t * p2.mTrimPathOffset; + returnPath.mStrokeMiterlimit = + t1 * p1.mStrokeMiterlimit + t * p2.mStrokeMiterlimit; + returnPath.mStrokelineCap = p1.mStrokelineCap; + if (returnPath.mStrokelineCap == null) { + returnPath.mStrokelineCap = p2.mStrokelineCap; + } + returnPath.mStrokelineJoin = p1.mStrokelineJoin; + if (returnPath.mStrokelineJoin == null) { + returnPath.mStrokelineJoin = p2.mStrokelineJoin; + } + returnPath.mFillRule = p1.mFillRule; + + returnPath.mStrokeColor = rgbInterpolate(t, p1.mStrokeColor, p2.mStrokeColor); + returnPath.mFillColor = rgbInterpolate(t, p1.mFillColor, p2.mFillColor); + returnPath.mStrokeWidth = t1 * p1.mStrokeWidth + t * p2.mStrokeWidth; + returnPath.mNumberOfStates = p1.mNumberOfStates; + for (int i = 0; i < returnPath.mNumberOfStates; i++) { + returnPath.mCheckState[i] = p1.mCheckState[i]; + returnPath.mCheckValue[i] = p1.mCheckValue[i]; + } + for (int i = 0; i < p2.mNumberOfStates; i++) { + returnPath.addStateFilter(p2.mCheckState[i], p2.mCheckValue[i]); + } + + int count = 0; + for (int i = 0; i < returnPath.mNumberOfStates; i++) { + if (returnPath.mCheckValue[i]) { + count++; + } + } + returnPath.mNumberOfTrue = count; + break; + case LIMIT_ROTATE: + returnPath.mRotate = t1 * p1.mRotate + t * p2.mRotate; + break; + case LIMIT_TRIM_PATH_END: + returnPath.mTrimPathEnd = t1 * p1.mTrimPathEnd + t * p2.mTrimPathEnd; + break; + case LIMIT_TRIM_PATH_OFFSET: + returnPath.mTrimPathOffset = t1 * p1.mTrimPathOffset + t * p2.mTrimPathOffset; + break; + case LIMIT_TRIM_PATH_START: + returnPath.mTrimPathStart = t1 * p1.mTrimPathStart + t * p2.mTrimPathStart; + break; + } + return returnPath; + } + + private static int rgbInterpolate(float t, int color1, int color2) { + int ret; + if (color1 == color2) { + return color2; + } + if (color1 == 0) { + return color2; + } + if (color2 == 0) { + return color1; + } + + float t1 = 1 - t; + ret = 0xFF & (((int) ((color1 & 0xFF) * t1 + (color2 & 0xFF) * t))); + color1 >>= 8; + color2 >>= 8; + + ret |= 0xFF00 & (((int) ((color1 & 0xFF) * t1 + (color2 & 0xFF) * t)) << 8); + color1 >>= 8; + color2 >>= 8; + ret |= 0xFF0000 & (((int) ((color1 & 0xFF) * t1 + (color2 & 0xFF) * t)) << 16); + color1 >>= 8; + color2 >>= 8; + ret |= 0xFF000000 & (((int) ((color1 & 0xFF) * t1 + (color2 & 0xFF) * t)) << 24); + + return ret; + } + + public boolean isVisible(int[] state) { + int match = 0; + for (int i = 0; i < state.length; i++) { + int v = getState(state[i]); + if (v != STATE_UNDEFINED) { + if (v==STATE_TRUE) { + match++; + } else { + return false; + } + } + } + return match == mNumberOfTrue; + } + } + + private static class VNode { + char type; + float[] params; + private static float[] current = new float[4]; + public VNode(char type, float[] params) { + this.type = type; + this.params = params; + } + + public VNode(VNode n) { + this.type = n.type; + this.params = Arrays.copyOf(n.params, n.params.length); + } + + public VNode(VNode n1, VNode n2, float t) { + this.type = n1.type; + this.params = new float[n1.params.length]; + interpolate(n1, n2, t); + } + + private boolean match(VNode n) { + if (n.type != type) { + return false; + } + return (params.length == n.params.length); + } + + public void interpolate(VNode n1, VNode n2, float t) { + for (int i = 0; i < n1.params.length; i++) { + params[i] = n1.params[i] * (1 - t) + n2.params[i] * t; + } + } + + private void nodeListToPath(VNode[] node, Path path) { + float[] current = new float[4]; + for (int i = 0; i < node.length; i++) { + addCommand(path, current, node[i].type, node[i].params); + } + } + + public static void createPath(VNode[] node, Path path) { + Arrays.fill(current,0); + for (int i = 0; i < node.length; i++) { + addCommand(path, current, node[i].type, node[i].params); + } + } + + private static void addCommand(Path path, float[] current, char cmd, float[] val) { + + int incr = 2; + float currentX = current[0]; + float currentY = current[1]; + float ctrlPointX = current[2]; + float ctrlPointY = current[3]; + + 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) { + // TODO: build test to prove all permutations work + 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) + path.rCubicTo(currentX - ctrlPointX, currentY - ctrlPointY, + 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) + path.cubicTo(2 * currentX - ctrlPointX, + 2 * currentY - ctrlPointY, + val[k + 0], + val[k + 1], + val[k + 2], + val[k + 3]); + currentX = val[k + 2]; + currentY = val[k + 3]; + ctrlPointX = val[k + 0]; + ctrlPointY = val[k + 1]; + break; + case 'q': // Draws a quadratic Bézier (relative) + path.rQuadTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3]); + currentX += val[k + 2]; + currentY += val[k + 3]; + ctrlPointX = val[k + 0]; + ctrlPointY = val[k + 1]; + break; + case 'Q': // Draws a quadratic Bézier + path.quadTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3]); + currentX = val[k + 2]; + currentY = val[k + 3]; + ctrlPointX = val[k + 0]; + ctrlPointY = val[k + 1]; + break; + case 't': // Draws a quadratic Bézier curve(reflective control point)(relative) + path.rQuadTo(currentX - ctrlPointX, currentY - ctrlPointY, + val[k + 0], val[k + 1]); + ctrlPointX = ctrlPointX + currentX; + ctrlPointY = ctrlPointY + currentY; + currentX += val[k + 0]; + currentY += val[k + 1]; + + break; + case 'T': // Draws a quadratic Bézier curve (reflective control point) + path.quadTo(currentX * 2 - ctrlPointX, currentY * 2 - ctrlPointY, + val[k + 0], val[k + 1]); + currentX = val[k + 0]; + currentY = val[k + 1]; // TODO: Check this logic + ctrlPointX = -(val[k + 0] - currentX); + ctrlPointY = -(val[k + 1] - currentY); + 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; + } + } + 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/tests/DynamicDrawableTest/Android.mk b/tests/DynamicDrawableTest/Android.mk new file mode 100644 index 0000000..5c51301 --- /dev/null +++ b/tests/DynamicDrawableTest/Android.mk @@ -0,0 +1,26 @@ +# +# Copyright (C) 2014 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := $(call all-subdir-java-files) + +LOCAL_PACKAGE_NAME := DynamicDrawableTest + +LOCAL_MODULE_TAGS := tests + +include $(BUILD_PACKAGE) diff --git a/tests/DynamicDrawableTest/AndroidManifest.xml b/tests/DynamicDrawableTest/AndroidManifest.xml new file mode 100644 index 0000000..4ca3e3c --- /dev/null +++ b/tests/DynamicDrawableTest/AndroidManifest.xml @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/DynamicDrawableTest/lint.xml b/tests/DynamicDrawableTest/lint.xml new file mode 100644 index 0000000..ee0eead --- /dev/null +++ b/tests/DynamicDrawableTest/lint.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/tests/DynamicDrawableTest/res/drawable-hdpi/icon.png b/tests/DynamicDrawableTest/res/drawable-hdpi/icon.png new file mode 100644 index 0000000..60fbdf5 Binary files /dev/null and b/tests/DynamicDrawableTest/res/drawable-hdpi/icon.png differ diff --git a/tests/DynamicDrawableTest/res/drawable/icon.png b/tests/DynamicDrawableTest/res/drawable/icon.png new file mode 100644 index 0000000..cb40a19 Binary files /dev/null and b/tests/DynamicDrawableTest/res/drawable/icon.png differ diff --git a/tests/DynamicDrawableTest/res/drawable/vector_drawable01.xml b/tests/DynamicDrawableTest/res/drawable/vector_drawable01.xml new file mode 100644 index 0000000..4f4b386 --- /dev/null +++ b/tests/DynamicDrawableTest/res/drawable/vector_drawable01.xml @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/DynamicDrawableTest/res/drawable/vector_drawable02.xml b/tests/DynamicDrawableTest/res/drawable/vector_drawable02.xml new file mode 100644 index 0000000..49906d17 --- /dev/null +++ b/tests/DynamicDrawableTest/res/drawable/vector_drawable02.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + diff --git a/tests/DynamicDrawableTest/res/drawable/vector_drawable03.xml b/tests/DynamicDrawableTest/res/drawable/vector_drawable03.xml new file mode 100644 index 0000000..137049d --- /dev/null +++ b/tests/DynamicDrawableTest/res/drawable/vector_drawable03.xml @@ -0,0 +1,137 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/DynamicDrawableTest/res/drawable/vector_drawable04.xml b/tests/DynamicDrawableTest/res/drawable/vector_drawable04.xml new file mode 100644 index 0000000..cffb73f --- /dev/null +++ b/tests/DynamicDrawableTest/res/drawable/vector_drawable04.xml @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/DynamicDrawableTest/res/drawable/vector_drawable05.xml b/tests/DynamicDrawableTest/res/drawable/vector_drawable05.xml new file mode 100644 index 0000000..bcf3ae6 --- /dev/null +++ b/tests/DynamicDrawableTest/res/drawable/vector_drawable05.xml @@ -0,0 +1,147 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/DynamicDrawableTest/res/drawable/vector_drawable06.xml b/tests/DynamicDrawableTest/res/drawable/vector_drawable06.xml new file mode 100644 index 0000000..73ff5e2 --- /dev/null +++ b/tests/DynamicDrawableTest/res/drawable/vector_drawable06.xml @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/DynamicDrawableTest/res/drawable/vector_drawable07.xml b/tests/DynamicDrawableTest/res/drawable/vector_drawable07.xml new file mode 100644 index 0000000..99d37ef --- /dev/null +++ b/tests/DynamicDrawableTest/res/drawable/vector_drawable07.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + diff --git a/tests/DynamicDrawableTest/res/drawable/vector_drawable08.xml b/tests/DynamicDrawableTest/res/drawable/vector_drawable08.xml new file mode 100644 index 0000000..f8a03d7 --- /dev/null +++ b/tests/DynamicDrawableTest/res/drawable/vector_drawable08.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/tests/DynamicDrawableTest/res/drawable/vector_drawable09.xml b/tests/DynamicDrawableTest/res/drawable/vector_drawable09.xml new file mode 100644 index 0000000..09934de --- /dev/null +++ b/tests/DynamicDrawableTest/res/drawable/vector_drawable09.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + diff --git a/tests/DynamicDrawableTest/res/drawable/vector_drawable10.xml b/tests/DynamicDrawableTest/res/drawable/vector_drawable10.xml new file mode 100644 index 0000000..f17f67a --- /dev/null +++ b/tests/DynamicDrawableTest/res/drawable/vector_drawable10.xml @@ -0,0 +1,114 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/DynamicDrawableTest/res/drawable/vector_drawable11.xml b/tests/DynamicDrawableTest/res/drawable/vector_drawable11.xml new file mode 100644 index 0000000..a1a03be --- /dev/null +++ b/tests/DynamicDrawableTest/res/drawable/vector_drawable11.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/DynamicDrawableTest/res/drawable/vector_drawable12.xml b/tests/DynamicDrawableTest/res/drawable/vector_drawable12.xml new file mode 100644 index 0000000..89748d5 --- /dev/null +++ b/tests/DynamicDrawableTest/res/drawable/vector_drawable12.xml @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/DynamicDrawableTest/res/drawable/vector_drawable13.xml b/tests/DynamicDrawableTest/res/drawable/vector_drawable13.xml new file mode 100644 index 0000000..43dda52 --- /dev/null +++ b/tests/DynamicDrawableTest/res/drawable/vector_drawable13.xml @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/DynamicDrawableTest/res/drawable/vector_drawable14.xml b/tests/DynamicDrawableTest/res/drawable/vector_drawable14.xml new file mode 100644 index 0000000..0f1f149 --- /dev/null +++ b/tests/DynamicDrawableTest/res/drawable/vector_drawable14.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + diff --git a/tests/DynamicDrawableTest/res/drawable/vector_drawable15.xml b/tests/DynamicDrawableTest/res/drawable/vector_drawable15.xml new file mode 100644 index 0000000..6bc946f --- /dev/null +++ b/tests/DynamicDrawableTest/res/drawable/vector_drawable15.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + diff --git a/tests/DynamicDrawableTest/res/drawable/vector_drawable16.xml b/tests/DynamicDrawableTest/res/drawable/vector_drawable16.xml new file mode 100644 index 0000000..c9c8e8a --- /dev/null +++ b/tests/DynamicDrawableTest/res/drawable/vector_drawable16.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + diff --git a/tests/DynamicDrawableTest/res/drawable/vector_drawable17.xml b/tests/DynamicDrawableTest/res/drawable/vector_drawable17.xml new file mode 100644 index 0000000..9427652 --- /dev/null +++ b/tests/DynamicDrawableTest/res/drawable/vector_drawable17.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + diff --git a/tests/DynamicDrawableTest/res/drawable/vector_drawable18.xml b/tests/DynamicDrawableTest/res/drawable/vector_drawable18.xml new file mode 100644 index 0000000..83dfbd2 --- /dev/null +++ b/tests/DynamicDrawableTest/res/drawable/vector_drawable18.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + diff --git a/tests/DynamicDrawableTest/res/drawable/vector_drawable19.xml b/tests/DynamicDrawableTest/res/drawable/vector_drawable19.xml new file mode 100644 index 0000000..013254f --- /dev/null +++ b/tests/DynamicDrawableTest/res/drawable/vector_drawable19.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + diff --git a/tests/DynamicDrawableTest/res/drawable/vector_drawable20.xml b/tests/DynamicDrawableTest/res/drawable/vector_drawable20.xml new file mode 100644 index 0000000..aba7e5f --- /dev/null +++ b/tests/DynamicDrawableTest/res/drawable/vector_drawable20.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + diff --git a/tests/DynamicDrawableTest/res/drawable/vector_icon_create.xml b/tests/DynamicDrawableTest/res/drawable/vector_icon_create.xml new file mode 100644 index 0000000..8897181 --- /dev/null +++ b/tests/DynamicDrawableTest/res/drawable/vector_icon_create.xml @@ -0,0 +1,29 @@ + + + + + + + + + + diff --git a/tests/DynamicDrawableTest/res/drawable/vector_icon_delete.xml b/tests/DynamicDrawableTest/res/drawable/vector_icon_delete.xml new file mode 100644 index 0000000..2c7ebbd --- /dev/null +++ b/tests/DynamicDrawableTest/res/drawable/vector_icon_delete.xml @@ -0,0 +1,29 @@ + + + + + + + + + + diff --git a/tests/DynamicDrawableTest/res/drawable/vector_icon_heart.xml b/tests/DynamicDrawableTest/res/drawable/vector_icon_heart.xml new file mode 100644 index 0000000..e4cf78c --- /dev/null +++ b/tests/DynamicDrawableTest/res/drawable/vector_icon_heart.xml @@ -0,0 +1,29 @@ + + + + + + + + + + diff --git a/tests/DynamicDrawableTest/res/drawable/vector_icon_schedule.xml b/tests/DynamicDrawableTest/res/drawable/vector_icon_schedule.xml new file mode 100644 index 0000000..cec12ba --- /dev/null +++ b/tests/DynamicDrawableTest/res/drawable/vector_icon_schedule.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + diff --git a/tests/DynamicDrawableTest/res/drawable/vector_icon_settings.xml b/tests/DynamicDrawableTest/res/drawable/vector_icon_settings.xml new file mode 100644 index 0000000..5fe1fb6 --- /dev/null +++ b/tests/DynamicDrawableTest/res/drawable/vector_icon_settings.xml @@ -0,0 +1,29 @@ + + + + + + + + + + diff --git a/tests/DynamicDrawableTest/res/drawable/vector_test01.xml b/tests/DynamicDrawableTest/res/drawable/vector_test01.xml new file mode 100644 index 0000000..6beb9d8 --- /dev/null +++ b/tests/DynamicDrawableTest/res/drawable/vector_test01.xml @@ -0,0 +1,31 @@ + + + + + + + + + + diff --git a/tests/DynamicDrawableTest/res/drawable/vector_test02.xml b/tests/DynamicDrawableTest/res/drawable/vector_test02.xml new file mode 100644 index 0000000..2c1a28e --- /dev/null +++ b/tests/DynamicDrawableTest/res/drawable/vector_test02.xml @@ -0,0 +1,31 @@ + + + + + + + + + + diff --git a/tests/DynamicDrawableTest/res/values/strings.xml b/tests/DynamicDrawableTest/res/values/strings.xml new file mode 100644 index 0000000..64163c2 --- /dev/null +++ b/tests/DynamicDrawableTest/res/values/strings.xml @@ -0,0 +1,18 @@ + + + + + diff --git a/tests/DynamicDrawableTest/res/values/styles.xml b/tests/DynamicDrawableTest/res/values/styles.xml new file mode 100644 index 0000000..460c0db --- /dev/null +++ b/tests/DynamicDrawableTest/res/values/styles.xml @@ -0,0 +1,16 @@ + + + diff --git a/tests/DynamicDrawableTest/src/com/android/test/dynamic/VectorCheckbox.java b/tests/DynamicDrawableTest/src/com/android/test/dynamic/VectorCheckbox.java new file mode 100644 index 0000000..1060d19 --- /dev/null +++ b/tests/DynamicDrawableTest/src/com/android/test/dynamic/VectorCheckbox.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package com.android.test.dynamic; + +import android.app.Activity; +import android.graphics.drawable.VectorDrawable; +import android.os.Bundle; +import android.util.Log; +import android.view.View; +import android.widget.Button; +import android.widget.CheckBox; +import android.widget.GridLayout; + +@SuppressWarnings({"UnusedDeclaration"}) +public class VectorCheckbox extends Activity { + private static final String LOGCAT = "VectorDrawable1"; + int[] icon = { + R.drawable.vector_drawable01, + R.drawable.vector_drawable02, + R.drawable.vector_drawable03, + R.drawable.vector_drawable04, + R.drawable.vector_drawable05, + R.drawable.vector_drawable06, + R.drawable.vector_drawable07, + R.drawable.vector_drawable08, + R.drawable.vector_drawable09, + R.drawable.vector_drawable10, + R.drawable.vector_drawable11, + R.drawable.vector_drawable12, + R.drawable.vector_drawable13, + R.drawable.vector_drawable14, + R.drawable.vector_drawable15, + R.drawable.vector_drawable16, + R.drawable.vector_drawable17, + R.drawable.vector_drawable18, + R.drawable.vector_drawable19, + R.drawable.vector_drawable20 + }; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + GridLayout container = new GridLayout(this); + container.setColumnCount(5); + container.setBackgroundColor(0xFF888888); + final Button []bArray = new Button[icon.length]; + + for (int i = 0; i < icon.length; i++) { + CheckBox checkBox = new CheckBox(this); + bArray[i] = checkBox; + checkBox.setWidth(200); + checkBox.setWidth(200); + checkBox.setButtonDrawable(icon[i]); + container.addView(checkBox); + } + setContentView(container); + } +} diff --git a/tests/DynamicDrawableTest/src/com/android/test/dynamic/VectorDrawable01.java b/tests/DynamicDrawableTest/src/com/android/test/dynamic/VectorDrawable01.java new file mode 100644 index 0000000..0ae4b6d --- /dev/null +++ b/tests/DynamicDrawableTest/src/com/android/test/dynamic/VectorDrawable01.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package com.android.test.dynamic; + +import android.app.Activity; +import android.graphics.drawable.VectorDrawable; +import android.os.Bundle; +import android.util.Log; +import android.view.View; +import android.widget.Button; +import android.widget.GridLayout; + +@SuppressWarnings({"UnusedDeclaration"}) +public class VectorDrawable01 extends Activity implements View.OnClickListener { + private static final String LOGCAT = "VectorDrawable1"; + int[] icon = { + R.drawable.vector_drawable01, + R.drawable.vector_drawable02, + R.drawable.vector_drawable03, + R.drawable.vector_drawable04, + R.drawable.vector_drawable05, + R.drawable.vector_drawable06, + R.drawable.vector_drawable07, + R.drawable.vector_drawable08, + R.drawable.vector_drawable09, + R.drawable.vector_drawable10, + R.drawable.vector_drawable11, + R.drawable.vector_drawable12, + R.drawable.vector_drawable13, + R.drawable.vector_drawable14, + R.drawable.vector_drawable15, + R.drawable.vector_drawable16, + R.drawable.vector_drawable17, + R.drawable.vector_drawable18, + R.drawable.vector_drawable19, + R.drawable.vector_drawable20 + }; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + GridLayout container = new GridLayout(this); + container.setColumnCount(5); + container.setBackgroundColor(0xFF888888); + final Button []bArray = new Button[icon.length]; + + for (int i = 0; i < icon.length; i++) { + Button button = new Button(this); + bArray[i] = button; + button.setWidth(200); + button.setWidth(200); + button.setBackgroundResource(icon[i]); + container.addView(button); + button.setOnClickListener(this); + } + Button b = new Button(this); + b.setText("Run All"); + b.setOnClickListener(new View.OnClickListener(){ + + @Override + public void onClick(View v) { + for (int i = 0; i < bArray.length; i++) { + VectorDrawable d = (VectorDrawable) bArray[i].getBackground(); + d.start(); + } + }}); + container.addView(b); + setContentView(container); + + } + + @Override + public void onClick(View v) { + VectorDrawable d = (VectorDrawable) v.getBackground(); + d.start(); + } + +} diff --git a/tests/DynamicDrawableTest/src/com/android/test/dynamic/VectorDrawableDupPerf.java b/tests/DynamicDrawableTest/src/com/android/test/dynamic/VectorDrawableDupPerf.java new file mode 100644 index 0000000..a00bc5e --- /dev/null +++ b/tests/DynamicDrawableTest/src/com/android/test/dynamic/VectorDrawableDupPerf.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package com.android.test.dynamic; + + +@SuppressWarnings({"UnusedDeclaration"}) +public class VectorDrawableDupPerf extends VectorDrawablePerformance { + { + icon = new int[]{ + R.drawable.vector_drawable04, + R.drawable.vector_drawable04, + R.drawable.vector_drawable04, + R.drawable.vector_drawable04, + R.drawable.vector_drawable04, + R.drawable.vector_drawable04, + R.drawable.vector_drawable04, + R.drawable.vector_drawable04, + R.drawable.vector_drawable04, + R.drawable.vector_drawable04, + R.drawable.vector_drawable04, + R.drawable.vector_drawable04, + R.drawable.vector_drawable04, + R.drawable.vector_drawable04, + R.drawable.vector_drawable04, + R.drawable.vector_drawable04, + R.drawable.vector_drawable04, + R.drawable.vector_drawable04, + R.drawable.vector_drawable04, + R.drawable.vector_drawable04, + }; + } +} diff --git a/tests/DynamicDrawableTest/src/com/android/test/dynamic/VectorDrawablePerformance.java b/tests/DynamicDrawableTest/src/com/android/test/dynamic/VectorDrawablePerformance.java new file mode 100644 index 0000000..9e8ea75 --- /dev/null +++ b/tests/DynamicDrawableTest/src/com/android/test/dynamic/VectorDrawablePerformance.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package com.android.test.dynamic; + +import android.app.Activity; +import android.content.res.Resources; +import android.graphics.drawable.VectorDrawable; +import android.os.Bundle; +import android.view.View; +import android.widget.TextView; +import android.widget.Button; +import android.widget.GridLayout; +import android.widget.ScrollView; +import java.text.DecimalFormat; + +@SuppressWarnings({"UnusedDeclaration"}) +public class VectorDrawablePerformance extends Activity implements View.OnClickListener { + private static final String LOGCAT = "VectorDrawable1"; + protected int[] icon = { + R.drawable.vector_drawable01, + R.drawable.vector_drawable02, + R.drawable.vector_drawable03, + R.drawable.vector_drawable04, + R.drawable.vector_drawable05, + R.drawable.vector_drawable06, + R.drawable.vector_drawable07, + R.drawable.vector_drawable08, + R.drawable.vector_drawable09, + R.drawable.vector_drawable10, + R.drawable.vector_drawable11, + R.drawable.vector_drawable12, + R.drawable.vector_drawable13, + R.drawable.vector_drawable14, + R.drawable.vector_drawable15, + R.drawable.vector_drawable16, + R.drawable.vector_drawable17, + R.drawable.vector_drawable18, + R.drawable.vector_drawable19, + R.drawable.vector_drawable20 + }; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + ScrollView scrollView = new ScrollView(this); + GridLayout container = new GridLayout(this); + scrollView.addView(container); + container.setColumnCount(5); + Resources res = this.getResources(); + container.setBackgroundColor(0xFF888888); + VectorDrawable []d = new VectorDrawable[icon.length]; + long time = android.os.SystemClock.elapsedRealtimeNanos(); + for (int i = 0; i < icon.length; i++) { + d[i] = VectorDrawable.create(res,icon[i]); + } + time = android.os.SystemClock.elapsedRealtimeNanos()-time; + TextView t = new TextView(this); + DecimalFormat df = new DecimalFormat("#.##"); + t.setText("avgL=" + df.format(time / (icon.length * 1000000.)) + " ms"); + t.setBackgroundColor(0xFF000000); + container.addView(t); + time = android.os.SystemClock.elapsedRealtimeNanos(); + for (int i = 0; i < icon.length; i++) { + Button button = new Button(this); + button.setWidth(200); + button.setWidth(200); + button.setBackgroundResource(icon[i]); + container.addView(button); + button.setOnClickListener(this); + } + setContentView(scrollView); + time = android.os.SystemClock.elapsedRealtimeNanos()-time; + t = new TextView(this); + t.setText("avgS=" + df.format(time / (icon.length * 1000000.)) + " ms"); + t.setBackgroundColor(0xFF000000); + container.addView(t); + } + + @Override + public void onClick(View v) { + VectorDrawable d = (VectorDrawable) v.getBackground(); + d.start(); + } +} diff --git a/tests/DynamicDrawableTest/src/com/android/test/dynamic/VectorDrawableStaticPerf.java b/tests/DynamicDrawableTest/src/com/android/test/dynamic/VectorDrawableStaticPerf.java new file mode 100644 index 0000000..9d3eded --- /dev/null +++ b/tests/DynamicDrawableTest/src/com/android/test/dynamic/VectorDrawableStaticPerf.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package com.android.test.dynamic; + +import android.app.Activity; +import android.content.res.Resources; +import android.graphics.drawable.VectorDrawable; +import android.os.Bundle; +import android.view.View; +import android.widget.TextView; +import android.widget.Button; +import android.widget.GridLayout; + +@SuppressWarnings({"UnusedDeclaration"}) +public class VectorDrawableStaticPerf extends VectorDrawablePerformance { + { + icon = new int[]{ + R.drawable.vector_icon_create, + R.drawable.vector_icon_delete, + R.drawable.vector_icon_heart, + R.drawable.vector_icon_schedule, + R.drawable.vector_icon_settings, + }; + } +} diff --git a/tests/DynamicDrawableTest/src/com/android/test/dynamic/VectorDrawableTest.java b/tests/DynamicDrawableTest/src/com/android/test/dynamic/VectorDrawableTest.java new file mode 100644 index 0000000..704d3d7 --- /dev/null +++ b/tests/DynamicDrawableTest/src/com/android/test/dynamic/VectorDrawableTest.java @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.test.dynamic; + +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.os.Bundle; +import android.view.View; +import android.widget.ListView; +import android.widget.SimpleAdapter; + +import java.text.Collator; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@SuppressWarnings("UnusedDeclaration") +public class VectorDrawableTest extends android.app.ListActivity { + private static final String EXTRA_PATH = "com.android.test.dynamic.Path"; + private static final String CATEGORY_HWUI_TEST = "com.android.test.dynamic.TEST"; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + Intent intent = getIntent(); + String path = intent.getStringExtra("com.android.test.hwui.Path"); + + if (path == null) { + path = ""; + } + + setListAdapter(new SimpleAdapter(this, getData(path), + android.R.layout.simple_list_item_1, new String[] { "title" }, + new int[] { android.R.id.text1 })); + getListView().setTextFilterEnabled(true); + } + + protected List> getData(String prefix) { + List> myData = new ArrayList>(); + + Intent mainIntent = new Intent(Intent.ACTION_MAIN, null); + mainIntent.addCategory(CATEGORY_HWUI_TEST); + + PackageManager pm = getPackageManager(); + List list = pm.queryIntentActivities(mainIntent, 0); + + if (null == list) + return myData; + + String[] prefixPath; + String prefixWithSlash = prefix; + + if (prefix.equals("")) { + prefixPath = null; + } else { + prefixPath = prefix.split("/"); + prefixWithSlash = prefix + "/"; + } + + int len = list.size(); + + Map entries = new HashMap(); + + for (int i = 0; i < len; i++) { + ResolveInfo info = list.get(i); + CharSequence labelSeq = info.loadLabel(pm); + String label = labelSeq != null + ? labelSeq.toString() + : info.activityInfo.name; + + if (prefixWithSlash.length() == 0 || label.startsWith(prefixWithSlash)) { + + String[] labelPath = label.split("/"); + + String nextLabel = prefixPath == null ? labelPath[0] : labelPath[prefixPath.length]; + + if ((prefixPath != null ? prefixPath.length : 0) == labelPath.length - 1) { + addItem(myData, nextLabel, activityIntent( + info.activityInfo.applicationInfo.packageName, + info.activityInfo.name)); + } else { + if (entries.get(nextLabel) == null) { + addItem(myData, nextLabel, browseIntent(prefix.equals("") ? + nextLabel : prefix + "/" + nextLabel)); + entries.put(nextLabel, true); + } + } + } + } + + Collections.sort(myData, sDisplayNameComparator); + + return myData; + } + + private final static Comparator> sDisplayNameComparator = + new Comparator>() { + private final Collator collator = Collator.getInstance(); + + public int compare(Map map1, Map map2) { + return collator.compare(map1.get("title"), map2.get("title")); + } + }; + + protected Intent activityIntent(String pkg, String componentName) { + Intent result = new Intent(); + result.setClassName(pkg, componentName); + return result; + } + + protected Intent browseIntent(String path) { + Intent result = new Intent(); + result.setClass(this, VectorDrawableTest.class); + result.putExtra(EXTRA_PATH, path); + return result; + } + + protected void addItem(List> data, String name, Intent intent) { + Map temp = new HashMap(); + temp.put("title", name); + temp.put("intent", intent); + data.add(temp); + } + + @Override + @SuppressWarnings({ "unchecked", "UnusedParameters" }) + protected void onListItemClick(ListView l, View v, int position, long id) { + Map map = (Map)l.getItemAtPosition(position); + + Intent intent = (Intent) map.get("intent"); + startActivity(intent); + } +} diff --git a/tests/DynamicDrawableTest/src/com/android/test/dynamic/VectorPathChecking.java b/tests/DynamicDrawableTest/src/com/android/test/dynamic/VectorPathChecking.java new file mode 100644 index 0000000..3430192 --- /dev/null +++ b/tests/DynamicDrawableTest/src/com/android/test/dynamic/VectorPathChecking.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package com.android.test.dynamic; + + +@SuppressWarnings({"UnusedDeclaration"}) +public class VectorPathChecking extends VectorDrawablePerformance { + { + icon = new int[]{ + R.drawable.vector_test01, + R.drawable.vector_test02 + }; + } +} -- cgit v1.1