From 4fcaa70c2362e58a3fb30d140f0a0eeda8e35b44 Mon Sep 17 00:00:00 2001 From: ztenghui Date: Wed, 4 Mar 2015 14:57:23 -0800 Subject: Add the support lib for VD and AVD By using auto namespace, we have all the functionality we want for support lib of VD and AVD. All the java files in the support lib are based upon the framework ones, with slight modification. The test cases are also from framework test, with namespace changed. Note that the animator part are still using android namespace since they are supported in v11. TODO: 1 integrate the AAPT support to use android namespace. 2 find the best way to integrate the framework VD AVD for L+. b/19200714 Change-Id: I10a80a89d26237ea780368e19929d83cd7678fff --- graphics/Android.mk | 16 + graphics/drawable/Android.mk | 41 + graphics/drawable/AndroidManifest.xml | 4 + .../drawable/AnimatedVectorDrawableCompat.java | 504 +++++++ graphics/drawable/res/values/attrs.xml | 183 +++ graphics/drawable/runavdtest.sh | 5 + graphics/drawable/runtest.sh | 5 + .../support/graphics/drawable/PathParser.java | 718 ++++++++++ .../graphics/drawable/VectorDrawableCompat.java | 1470 ++++++++++++++++++++ graphics/drawable/testanimated/Android.mk | 35 + graphics/drawable/testanimated/AndroidManifest.xml | 31 + .../res/anim/alpha_animation_progress_bar.xml | 24 + .../res/anim/animation_grouping_1_01.xml | 22 + .../res/anim/trim_path_animation_progress_bar.xml | 45 + .../animation_vector_drawable_grouping_1.xml | 27 + .../res/drawable/animation_vector_progress_bar.xml | 26 + .../testanimated/res/drawable/app_sample_code.png | Bin 0 -> 3608 bytes .../res/drawable/vector_drawable_grouping_1.xml | 53 + .../res/drawable/vector_drawable_progress_bar.xml | 50 + .../drawable/testanimated/res/values/strings.xml | 28 + .../test/vectordrawable/TestAVDActivity.java | 98 ++ graphics/drawable/teststatic/Android.mk | 38 + graphics/drawable/teststatic/AndroidManifest.xml | 32 + .../teststatic/res/drawable/app_sample_code.png | Bin 0 -> 3608 bytes .../teststatic/res/drawable/vector_drawable01.xml | 34 + .../teststatic/res/drawable/vector_drawable02.xml | 37 + .../teststatic/res/drawable/vector_drawable03.xml | 72 + .../teststatic/res/drawable/vector_drawable04.xml | 58 + .../teststatic/res/drawable/vector_drawable05.xml | 44 + .../teststatic/res/drawable/vector_drawable06.xml | 49 + .../teststatic/res/drawable/vector_drawable07.xml | 30 + .../teststatic/res/drawable/vector_drawable08.xml | 30 + .../teststatic/res/drawable/vector_drawable09.xml | 33 + .../teststatic/res/drawable/vector_drawable10.xml | 43 + .../teststatic/res/drawable/vector_drawable11.xml | 36 + .../teststatic/res/drawable/vector_drawable12.xml | 98 ++ .../teststatic/res/drawable/vector_drawable13.xml | 38 + .../teststatic/res/drawable/vector_drawable14.xml | 39 + .../teststatic/res/drawable/vector_drawable15.xml | 35 + .../teststatic/res/drawable/vector_drawable16.xml | 48 + .../teststatic/res/drawable/vector_drawable17.xml | 30 + .../teststatic/res/drawable/vector_drawable18.xml | 32 + .../teststatic/res/drawable/vector_drawable19.xml | 34 + .../teststatic/res/drawable/vector_drawable20.xml | 35 + .../teststatic/res/drawable/vector_drawable21.xml | 48 + .../teststatic/res/drawable/vector_drawable22.xml | 69 + .../teststatic/res/drawable/vector_drawable23.xml | 83 ++ .../teststatic/res/drawable/vector_drawable24.xml | 83 ++ .../teststatic/res/drawable/vector_drawable25.xml | 83 ++ .../teststatic/res/drawable/vector_drawable26.xml | 46 + .../teststatic/res/drawable/vector_drawable27.xml | 46 + .../teststatic/res/drawable/vector_drawable28.xml | 47 + .../teststatic/res/drawable/vector_drawable29.xml | 29 + .../teststatic/res/drawable/vector_drawable30.xml | 29 + .../teststatic/res/raw/vector_drawable01.xml | 32 + .../drawable/teststatic/res/values/strings.xml | 28 + .../support/test/vectordrawable/TestActivity.java | 128 ++ 57 files changed, 5061 insertions(+) create mode 100644 graphics/Android.mk create mode 100644 graphics/drawable/Android.mk create mode 100644 graphics/drawable/AndroidManifest.xml create mode 100644 graphics/drawable/animated/src/android/support/graphics/drawable/AnimatedVectorDrawableCompat.java create mode 100644 graphics/drawable/res/values/attrs.xml create mode 100755 graphics/drawable/runavdtest.sh create mode 100755 graphics/drawable/runtest.sh create mode 100644 graphics/drawable/static/src/android/support/graphics/drawable/PathParser.java create mode 100644 graphics/drawable/static/src/android/support/graphics/drawable/VectorDrawableCompat.java create mode 100644 graphics/drawable/testanimated/Android.mk create mode 100644 graphics/drawable/testanimated/AndroidManifest.xml create mode 100644 graphics/drawable/testanimated/res/anim/alpha_animation_progress_bar.xml create mode 100644 graphics/drawable/testanimated/res/anim/animation_grouping_1_01.xml create mode 100644 graphics/drawable/testanimated/res/anim/trim_path_animation_progress_bar.xml create mode 100644 graphics/drawable/testanimated/res/drawable/animation_vector_drawable_grouping_1.xml create mode 100644 graphics/drawable/testanimated/res/drawable/animation_vector_progress_bar.xml create mode 100755 graphics/drawable/testanimated/res/drawable/app_sample_code.png create mode 100644 graphics/drawable/testanimated/res/drawable/vector_drawable_grouping_1.xml create mode 100644 graphics/drawable/testanimated/res/drawable/vector_drawable_progress_bar.xml create mode 100644 graphics/drawable/testanimated/res/values/strings.xml create mode 100644 graphics/drawable/testanimated/src/android/support/test/vectordrawable/TestAVDActivity.java create mode 100644 graphics/drawable/teststatic/Android.mk create mode 100644 graphics/drawable/teststatic/AndroidManifest.xml create mode 100755 graphics/drawable/teststatic/res/drawable/app_sample_code.png create mode 100644 graphics/drawable/teststatic/res/drawable/vector_drawable01.xml create mode 100644 graphics/drawable/teststatic/res/drawable/vector_drawable02.xml create mode 100644 graphics/drawable/teststatic/res/drawable/vector_drawable03.xml create mode 100644 graphics/drawable/teststatic/res/drawable/vector_drawable04.xml create mode 100644 graphics/drawable/teststatic/res/drawable/vector_drawable05.xml create mode 100644 graphics/drawable/teststatic/res/drawable/vector_drawable06.xml create mode 100644 graphics/drawable/teststatic/res/drawable/vector_drawable07.xml create mode 100644 graphics/drawable/teststatic/res/drawable/vector_drawable08.xml create mode 100644 graphics/drawable/teststatic/res/drawable/vector_drawable09.xml create mode 100644 graphics/drawable/teststatic/res/drawable/vector_drawable10.xml create mode 100644 graphics/drawable/teststatic/res/drawable/vector_drawable11.xml create mode 100644 graphics/drawable/teststatic/res/drawable/vector_drawable12.xml create mode 100644 graphics/drawable/teststatic/res/drawable/vector_drawable13.xml create mode 100644 graphics/drawable/teststatic/res/drawable/vector_drawable14.xml create mode 100644 graphics/drawable/teststatic/res/drawable/vector_drawable15.xml create mode 100644 graphics/drawable/teststatic/res/drawable/vector_drawable16.xml create mode 100644 graphics/drawable/teststatic/res/drawable/vector_drawable17.xml create mode 100644 graphics/drawable/teststatic/res/drawable/vector_drawable18.xml create mode 100644 graphics/drawable/teststatic/res/drawable/vector_drawable19.xml create mode 100644 graphics/drawable/teststatic/res/drawable/vector_drawable20.xml create mode 100644 graphics/drawable/teststatic/res/drawable/vector_drawable21.xml create mode 100644 graphics/drawable/teststatic/res/drawable/vector_drawable22.xml create mode 100644 graphics/drawable/teststatic/res/drawable/vector_drawable23.xml create mode 100644 graphics/drawable/teststatic/res/drawable/vector_drawable24.xml create mode 100644 graphics/drawable/teststatic/res/drawable/vector_drawable25.xml create mode 100644 graphics/drawable/teststatic/res/drawable/vector_drawable26.xml create mode 100644 graphics/drawable/teststatic/res/drawable/vector_drawable27.xml create mode 100644 graphics/drawable/teststatic/res/drawable/vector_drawable28.xml create mode 100644 graphics/drawable/teststatic/res/drawable/vector_drawable29.xml create mode 100644 graphics/drawable/teststatic/res/drawable/vector_drawable30.xml create mode 100644 graphics/drawable/teststatic/res/raw/vector_drawable01.xml create mode 100644 graphics/drawable/teststatic/res/values/strings.xml create mode 100644 graphics/drawable/teststatic/src/android/support/test/vectordrawable/TestActivity.java (limited to 'graphics') diff --git a/graphics/Android.mk b/graphics/Android.mk new file mode 100644 index 0000000000..365b3b1b9f --- /dev/null +++ b/graphics/Android.mk @@ -0,0 +1,16 @@ +# Copyright (C) 2015 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 $(call all-makefiles-under,$(LOCAL_PATH)) diff --git a/graphics/drawable/Android.mk b/graphics/drawable/Android.mk new file mode 100644 index 0000000000..63e93b7d01 --- /dev/null +++ b/graphics/drawable/Android.mk @@ -0,0 +1,41 @@ +# Copyright (C) 2015 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) + +#static vector drawable library +include $(CLEAR_VARS) +LOCAL_MODULE := android-support-v7-vectordrawable +LOCAL_SDK_VERSION := 7 +LOCAL_SRC_FILES := $(call all-java-files-under, static) + +LOCAL_JAVA_LIBRARIES := android-support-v4 + +LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res \ + +include $(BUILD_STATIC_JAVA_LIBRARY) + +#Animated vector drawable library +include $(CLEAR_VARS) +LOCAL_MODULE := android-support-v11-animatedvectordrawable +LOCAL_SDK_VERSION := 11 +LOCAL_SRC_FILES := $(call all-java-files-under, animated) + +LOCAL_JAVA_LIBRARIES := android-support-v4 + +LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res \ + +LOCAL_STATIC_JAVA_LIBRARIES := android-support-v7-vectordrawable + +include $(BUILD_STATIC_JAVA_LIBRARY) \ No newline at end of file diff --git a/graphics/drawable/AndroidManifest.xml b/graphics/drawable/AndroidManifest.xml new file mode 100644 index 0000000000..83124c76cf --- /dev/null +++ b/graphics/drawable/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + diff --git a/graphics/drawable/animated/src/android/support/graphics/drawable/AnimatedVectorDrawableCompat.java b/graphics/drawable/animated/src/android/support/graphics/drawable/AnimatedVectorDrawableCompat.java new file mode 100644 index 0000000000..78ef62d1d6 --- /dev/null +++ b/graphics/drawable/animated/src/android/support/graphics/drawable/AnimatedVectorDrawableCompat.java @@ -0,0 +1,504 @@ +/* + * Copyright (C) 2015 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.support.graphics.drawable; + +import android.animation.Animator; +import android.animation.AnimatorInflater; +import android.animation.ObjectAnimator; +import android.content.Context; +import android.content.res.ColorStateList; +import android.content.res.Resources; +import android.content.res.Resources.Theme; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.ColorFilter; +import android.graphics.PorterDuff.Mode; +import android.graphics.Rect; +import android.graphics.drawable.Animatable; +import android.graphics.drawable.Drawable; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.DrawableRes; +import android.support.v4.util.ArrayMap; +import android.util.AttributeSet; +import android.util.Log; +import android.util.Xml; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.util.ArrayList; + +/** + * This class uses {@link android.animation.ObjectAnimator} and + * {@link android.animation.AnimatorSet} to animate the properties of a + * {@link android.graphics.drawable.VectorDrawableCompat} to create an animated drawable. + *

+ * AnimatedVectorDrawableCompat are normally defined as 3 separate XML files. + *

+ *

+ * First is the XML file for {@link android.graphics.drawable.VectorDrawableCompat}. Note that we + * allow the animation to happen on the group's attributes and path's attributes, which requires they + * are uniquely named in this XML file. Groups and paths without animations do not need names. + *

+ *
  • Here is a simple VectorDrawable in this vectordrawable.xml file. + *
    + * <vector xmlns:android="http://schemas.android.com/apk/res/android"
    + *     android:height="64dp"
    + *     android:width="64dp"
    + *     android:viewportHeight="600"
    + *     android:viewportWidth="600" >
    + *     <group
    + *         android:name="rotationGroup"
    + *         android:pivotX="300.0"
    + *         android:pivotY="300.0"
    + *         android:rotation="45.0" >
    + *         <path
    + *             android:name="v"
    + *             android:fillColor="#000000"
    + *             android:pathData="M300,70 l 0,-70 70,70 0,0 -70,70z" />
    + *     </group>
    + * </vector>
    + * 
  • + *

    + * Second is the AnimatedVectorDrawableCompat's XML file, which defines the target + * VectorDrawableCompat, the target paths and groups to animate, the properties of the path and + * group to animate and the animations defined as the ObjectAnimators or AnimatorSets. + *

    + *
  • Here is a simple AnimatedVectorDrawable defined in this avd.xml file. + * Note how we use the names to refer to the groups and paths in the vectordrawable.xml. + *
    + * <animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
    + *   android:drawable="@drawable/vectordrawable" >
    + *     <target
    + *         android:name="rotationGroup"
    + *         android:animation="@anim/rotation" />
    + *     <target
    + *         android:name="v"
    + *         android:animation="@anim/path_morph" />
    + * </animated-vector>
    + * 
  • + *

    + * Last is the Animator XML file, which is the same as a normal ObjectAnimator or AnimatorSet. To + * complete this example, here are the 2 animator files used in avd.xml: rotation.xml and + * path_morph.xml. + *

    + *
  • Here is the rotation.xml, which will rotate the target group for 360 degrees. + *
    + * <objectAnimator
    + *     android:duration="6000"
    + *     android:propertyName="rotation"
    + *     android:valueFrom="0"
    + *     android:valueTo="360" />
    + * 
  • + *
  • Here is the path_morph.xml, which will morph the path from one shape to + * the other. Note that the paths must be compatible for morphing. + * In more details, the paths should have exact same length of commands, and + * exact same length of parameters for each commands. + * Note that the path strings are better stored in strings.xml for reusing. + *
    + * <set xmlns:android="http://schemas.android.com/apk/res/android">
    + *     <objectAnimator
    + *         android:duration="3000"
    + *         android:propertyName="pathData"
    + *         android:valueFrom="M300,70 l 0,-70 70,70 0,0   -70,70z"
    + *         android:valueTo="M300,70 l 0,-70 70,0  0,140 -70,0 z"
    + *         android:valueType="pathType"/>
    + * </set>
    + * 
  • + * + * @attr ref android.R.styleable#AnimatedVectorDrawableCompat_drawable + * @attr ref android.R.styleable#AnimatedVectorDrawableCompatTarget_name + * @attr ref android.R.styleable#AnimatedVectorDrawableCompatTarget_animation + */ +public class AnimatedVectorDrawableCompat extends Drawable implements Animatable { + private static final String LOGTAG = "AnimatedVectorDrawableCompat"; + + private static final String ANIMATED_VECTOR = "animated-vector"; + private static final String TARGET = "target"; + + private static final boolean DBG_ANIMATION_VECTOR_DRAWABLE = false; + + private AnimatedVectorDrawableCompatState mAnimatedVectorState; + + private boolean mMutated; + + private Context mContext; + + // Currently the only useful ctor. + public AnimatedVectorDrawableCompat(Context context) { + this(context, null, null); + } + + private AnimatedVectorDrawableCompat(Context context, AnimatedVectorDrawableCompatState state, + Resources res) { + mContext = context; + if (state != null) { + mAnimatedVectorState = state; + } else { + mAnimatedVectorState = new AnimatedVectorDrawableCompatState(context, state, mCallback, + res); + } + } + + @Override + public Drawable mutate() { + if (!mMutated && super.mutate() == this) { + mAnimatedVectorState = + new AnimatedVectorDrawableCompatState(null, mAnimatedVectorState, mCallback, + null); + mMutated = true; + } + return this; + } + + + /** + * Create a AnimatedVectorDrawableCompat object. + * + * @param context the context for creating the animators. + * @param resId the resource ID for AnimatedVectorDrawableCompat object. + * @return a new AnimatedVectorDrawableCompat or null if parsing error is found. + */ + @Nullable + public static AnimatedVectorDrawableCompat create(@NonNull Context context, + @DrawableRes int resId) { + Resources resources = context.getResources(); + try { + final XmlPullParser parser = resources.getXml(resId); + final AttributeSet attrs = Xml.asAttributeSet(parser); + int type; + while ((type = parser.next()) != XmlPullParser.START_TAG + && type != XmlPullParser.END_DOCUMENT) { + // Empty loop + } + if (type != XmlPullParser.START_TAG) { + throw new XmlPullParserException("No start tag found"); + } + + final AnimatedVectorDrawableCompat drawable = new AnimatedVectorDrawableCompat(context); + drawable.inflate(resources, parser, attrs, context.getTheme()); + + return drawable; + } catch (XmlPullParserException e) { + Log.e(LOGTAG, "parser error", e); + } catch (IOException e) { + Log.e(LOGTAG, "parser error", e); + } + return null; + } + + @Override + public ConstantState getConstantState() { + mAnimatedVectorState.mChangingConfigurations = getChangingConfigurations(); + return mAnimatedVectorState; + } + + @Override + public int getChangingConfigurations() { + return super.getChangingConfigurations() | mAnimatedVectorState.mChangingConfigurations; + } + + @Override + public void draw(Canvas canvas) { + mAnimatedVectorState.mVectorDrawable.draw(canvas); + if (isStarted()) { + invalidateSelf(); + } + } + + @Override + protected void onBoundsChange(Rect bounds) { + mAnimatedVectorState.mVectorDrawable.setBounds(bounds); + } + + @Override + protected boolean onStateChange(int[] state) { + return mAnimatedVectorState.mVectorDrawable.setState(state); + } + + @Override + protected boolean onLevelChange(int level) { + return mAnimatedVectorState.mVectorDrawable.setLevel(level); + } + + // @Override + public int getAlpha() { + return mAnimatedVectorState.mVectorDrawable.getAlpha(); + } + + // @Override + public void setAlpha(int alpha) { + mAnimatedVectorState.mVectorDrawable.setAlpha(alpha); + } + + @Override + public void setColorFilter(ColorFilter colorFilter) { + mAnimatedVectorState.mVectorDrawable.setColorFilter(colorFilter); + } + + public void setTintList(ColorStateList tint) { + mAnimatedVectorState.mVectorDrawable.setTintList(tint); + } + + public void setTintMode(Mode tintMode) { + mAnimatedVectorState.mVectorDrawable.setTintMode(tintMode); + } + + @Override + public boolean setVisible(boolean visible, boolean restart) { + mAnimatedVectorState.mVectorDrawable.setVisible(visible, restart); + return super.setVisible(visible, restart); + } + + @Override + public boolean isStateful() { + return mAnimatedVectorState.mVectorDrawable.isStateful(); + } + + @Override + public int getOpacity() { + return mAnimatedVectorState.mVectorDrawable.getOpacity(); + } + + public int getIntrinsicWidth() { + return mAnimatedVectorState.mVectorDrawable.getIntrinsicWidth(); + } + + public int getIntrinsicHeight() { + return mAnimatedVectorState.mVectorDrawable.getIntrinsicHeight(); + } + + /** + * Obtains styled attributes from the theme, if available, or unstyled + * resources if the theme is null. + */ + static TypedArray obtainAttributes( + Resources res, Theme theme, AttributeSet set, int[] attrs) { + if (theme == null) { + return res.obtainAttributes(set, attrs); + } + return theme.obtainStyledAttributes(set, attrs, 0, 0); + } + + public void inflate(Resources res, XmlPullParser parser, AttributeSet attrs, Theme theme) + throws XmlPullParserException, IOException { + int eventType = parser.getEventType(); + float pathErrorScale = 1; + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + final String tagName = parser.getName(); + if (DBG_ANIMATION_VECTOR_DRAWABLE) { + Log.v(LOGTAG, "tagName is " + tagName); + } + if (ANIMATED_VECTOR.equals(tagName)) { + final TypedArray a = + obtainAttributes(res, theme, attrs, R.styleable.AnimatedVectorDrawable); + + int drawableRes = a.getResourceId(R.styleable.AnimatedVectorDrawable_drawable, + 0); + if (DBG_ANIMATION_VECTOR_DRAWABLE) { + Log.v(LOGTAG, "drawableRes is " + drawableRes); + } + if (drawableRes != 0) { + VectorDrawableCompat vectorDrawable = VectorDrawableCompat.create(res, + drawableRes, theme); + vectorDrawable.setAllowCaching(false); + vectorDrawable.setCallback(mCallback); + pathErrorScale = vectorDrawable.getPixelSize(); + if (mAnimatedVectorState.mVectorDrawable != null) { + mAnimatedVectorState.mVectorDrawable.setCallback(null); + } + mAnimatedVectorState.mVectorDrawable = vectorDrawable; + } + a.recycle(); + } else if (TARGET.equals(tagName)) { + final TypedArray a = + res.obtainAttributes(attrs, R.styleable.AnimatedVectorDrawableTarget); + final String target = a.getString( + R.styleable.AnimatedVectorDrawableTarget_name); + + int id = a.getResourceId(R.styleable.AnimatedVectorDrawableTarget_animation, 0); + if (id != 0) { + Animator objectAnimator = AnimatorInflater.loadAnimator(mContext, id); + setupAnimatorsForTarget(target, objectAnimator); + } + a.recycle(); + } + } + + eventType = parser.next(); + } + } + + @Override + public void inflate(Resources res, XmlPullParser parser, AttributeSet attrs) + throws XmlPullParserException, IOException { + inflate(res, parser, attrs, null); + } + + public boolean canApplyTheme() { + return false; + } + + private static class AnimatedVectorDrawableCompatState extends ConstantState { + int mChangingConfigurations; + VectorDrawableCompat mVectorDrawable; + ArrayList mAnimators; + ArrayMap mTargetNameMap; + Context mContext; + + public AnimatedVectorDrawableCompatState(Context context, + AnimatedVectorDrawableCompatState copy, Callback owner, Resources res) { + if (copy != null) { + mChangingConfigurations = copy.mChangingConfigurations; + if (copy.mVectorDrawable != null) { + final ConstantState cs = copy.mVectorDrawable.getConstantState(); + if (res != null) { + mVectorDrawable = (VectorDrawableCompat) cs.newDrawable(res); + } else { + mVectorDrawable = (VectorDrawableCompat) cs.newDrawable(); + } + mVectorDrawable = (VectorDrawableCompat) mVectorDrawable.mutate(); + mVectorDrawable.setCallback(owner); + mVectorDrawable.setBounds(copy.mVectorDrawable.getBounds()); + mVectorDrawable.setAllowCaching(false); + } + if (copy.mAnimators != null) { + final int numAnimators = copy.mAnimators.size(); + mAnimators = new ArrayList(numAnimators); + mTargetNameMap = new ArrayMap(numAnimators); + for (int i = 0; i < numAnimators; ++i) { + Animator anim = copy.mAnimators.get(i); + Animator animClone = anim.clone(); + String targetName = copy.mTargetNameMap.get(anim); + Object targetObject = mVectorDrawable.getTargetByName(targetName); + animClone.setTarget(targetObject); + mAnimators.add(animClone); + mTargetNameMap.put(animClone, targetName); + } + } + } + + if (context != null) { + mContext = context; + } else { + mContext = copy.mContext; + } + + } + + @Override + public Drawable newDrawable() { + return new AnimatedVectorDrawableCompat(mContext, this, null); + } + + @Override + public Drawable newDrawable(Resources res) { + return new AnimatedVectorDrawableCompat(mContext, this, res); + } + + @Override + public int getChangingConfigurations() { + return mChangingConfigurations; + } + } + + private void setupAnimatorsForTarget(String name, Animator animator) { + Object target = mAnimatedVectorState.mVectorDrawable.getTargetByName(name); + animator.setTarget(target); + if (mAnimatedVectorState.mAnimators == null) { + mAnimatedVectorState.mAnimators = new ArrayList(); + mAnimatedVectorState.mTargetNameMap = new ArrayMap(); + } + mAnimatedVectorState.mAnimators.add(animator); + mAnimatedVectorState.mTargetNameMap.put(animator, name); + if (DBG_ANIMATION_VECTOR_DRAWABLE) { + Log.v(LOGTAG, "add animator for target " + name + " " + animator); + } + } + + @Override + public boolean isRunning() { + final ArrayList animators = mAnimatedVectorState.mAnimators; + final int size = animators.size(); + for (int i = 0; i < size; i++) { + final Animator animator = animators.get(i); + if (animator.isRunning()) { + return true; + } + } + return false; + } + + private boolean isStarted() { + final ArrayList animators = mAnimatedVectorState.mAnimators; + if (animators == null) { + return false; + } + final int size = animators.size(); + for (int i = 0; i < size; i++) { + final Animator animator = animators.get(i); + if (animator.isRunning()) { + return true; + } + } + return false; + } + + @Override + public void start() { + // If any one of the animator has not ended, do nothing. + if (isStarted()) { + return; + } + // Otherwise, kick off every animator. + final ArrayList animators = mAnimatedVectorState.mAnimators; + final int size = animators.size(); + for (int i = 0; i < size; i++) { + final Animator animator = animators.get(i); + animator.start(); + } + invalidateSelf(); + } + + @Override + public void stop() { + final ArrayList animators = mAnimatedVectorState.mAnimators; + final int size = animators.size(); + for (int i = 0; i < size; i++) { + final Animator animator = animators.get(i); + animator.end(); + } + } + + private final Callback mCallback = new Callback() { + @Override + public void invalidateDrawable(Drawable who) { + invalidateSelf(); + } + + @Override + public void scheduleDrawable(Drawable who, Runnable what, long when) { + scheduleSelf(what, when); + } + + @Override + public void unscheduleDrawable(Drawable who, Runnable what) { + unscheduleSelf(what); + } + }; +} diff --git a/graphics/drawable/res/values/attrs.xml b/graphics/drawable/res/values/attrs.xml new file mode 100644 index 0000000000..b54c9a69c1 --- /dev/null +++ b/graphics/drawable/res/values/attrs.xml @@ -0,0 +1,183 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/graphics/drawable/runavdtest.sh b/graphics/drawable/runavdtest.sh new file mode 100755 index 0000000000..023ad3e8e9 --- /dev/null +++ b/graphics/drawable/runavdtest.sh @@ -0,0 +1,5 @@ +. ../../../../build/envsetup.sh +mmm -j20 . && mmm -j20 ./testanimated/ && \ +adb install -r $OUT/data/app/AndroidAnimatedVectorDrawableTests/AndroidAnimatedVectorDrawableTests.apk && \ +adb shell am start -n android.support.test.vectordrawable/android.support.test.vectordrawable.TestAVDActivity + diff --git a/graphics/drawable/runtest.sh b/graphics/drawable/runtest.sh new file mode 100755 index 0000000000..6f697801ba --- /dev/null +++ b/graphics/drawable/runtest.sh @@ -0,0 +1,5 @@ +. ../../../../build/envsetup.sh +mmm -j20 . && mmm -j20 ./teststatic/ && \ +adb install -r $OUT/data/app/AndroidVectorDrawableTests/AndroidVectorDrawableTests.apk && \ +adb shell am start -n android.support.test.vectordrawable/android.support.test.vectordrawable.TestActivity + diff --git a/graphics/drawable/static/src/android/support/graphics/drawable/PathParser.java b/graphics/drawable/static/src/android/support/graphics/drawable/PathParser.java new file mode 100644 index 0000000000..a92c3e578d --- /dev/null +++ b/graphics/drawable/static/src/android/support/graphics/drawable/PathParser.java @@ -0,0 +1,718 @@ +/* + * Copyright (C) 2015 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.support.graphics.drawable; + +import android.graphics.Path; +import android.util.Log; + +import java.util.ArrayList; + +// This class is a duplicate from the PathParser.java of frameworks/base, with slight +// update on incompatible API like copyOfRange(). +class PathParser { + private static final String LOGTAG = "PathParser"; + + // Copy from Arrays.copyOfRange() which is only available from API level 9. + /** + * Copies elements from {@code original} into a new array, from indexes start (inclusive) to + * end (exclusive). The original order of elements is preserved. + * If {@code end} is greater than {@code original.length}, the result is padded + * with the value {@code 0.0f}. + * + * @param original the original array + * @param start the start index, inclusive + * @param end the end index, exclusive + * @return the new array + * @throws ArrayIndexOutOfBoundsException if {@code start < 0 || start > original.length} + * @throws IllegalArgumentException if {@code start > end} + * @throws NullPointerException if {@code original == null} + */ + private static float[] copyOfRange(float[] original, int start, int end) { + if (start > end) { + throw new IllegalArgumentException(); + } + int originalLength = original.length; + if (start < 0 || start > originalLength) { + throw new ArrayIndexOutOfBoundsException(); + } + int resultLength = end - start; + int copyLength = Math.min(resultLength, originalLength - start); + float[] result = new float[resultLength]; + System.arraycopy(original, start, result, 0, copyLength); + return result; + } + + /** + * @param pathData The string representing a path, the same as "d" string in svg file. + * @return the generated Path object. + */ + public static Path createPathFromPathData(String pathData) { + Path path = new Path(); + PathDataNode[] nodes = createNodesFromPathData(pathData); + if (nodes != null) { + try { + PathDataNode.nodesToPath(nodes, path); + } catch (RuntimeException e) { + throw new RuntimeException("Error in parsing " + pathData, e); + } + return path; + } + return null; + } + + /** + * @param pathData The string representing a path, the same as "d" string in svg file. + * @return an array of the PathDataNode. + */ + public static PathDataNode[] createNodesFromPathData(String pathData) { + if (pathData == null) { + return null; + } + int start = 0; + int end = 1; + + ArrayList list = new ArrayList(); + while (end < pathData.length()) { + end = nextStart(pathData, end); + String s = pathData.substring(start, end).trim(); + if (s.length() > 0) { + float[] val = getFloats(s); + addNode(list, s.charAt(0), val); + } + + start = end; + end++; + } + if ((end - start) == 1 && start < pathData.length()) { + addNode(list, pathData.charAt(start), new float[0]); + } + return list.toArray(new PathDataNode[list.size()]); + } + + /** + * @param source The array of PathDataNode to be duplicated. + * @return a deep copy of the source. + */ + public static PathDataNode[] deepCopyNodes(PathDataNode[] source) { + if (source == null) { + return null; + } + PathDataNode[] copy = new PathParser.PathDataNode[source.length]; + for (int i = 0; i < source.length; i ++) { + copy[i] = new PathDataNode(source[i]); + } + return copy; + } + + /** + * @param nodesFrom The source path represented in an array of PathDataNode + * @param nodesTo The target path represented in an array of PathDataNode + * @return whether the nodesFrom can morph into nodesTo + */ + public static boolean canMorph(PathDataNode[] nodesFrom, PathDataNode[] nodesTo) { + if (nodesFrom == null || nodesTo == null) { + return false; + } + + if (nodesFrom.length != nodesTo.length) { + return false; + } + + for (int i = 0; i < nodesFrom.length; i ++) { + if (nodesFrom[i].mType != nodesTo[i].mType + || nodesFrom[i].mParams.length != nodesTo[i].mParams.length) { + return false; + } + } + return true; + } + + /** + * Update the target's data to match the source. + * Before calling this, make sure canMorph(target, source) is true. + * + * @param target The target path represented in an array of PathDataNode + * @param source The source path represented in an array of PathDataNode + */ + public static void updateNodes(PathDataNode[] target, PathDataNode[] source) { + for (int i = 0; i < source.length; i ++) { + target[i].mType = source[i].mType; + for (int j = 0; j < source[i].mParams.length; j ++) { + target[i].mParams[j] = source[i].mParams[j]; + } + } + } + + private static int nextStart(String s, int end) { + char c; + + while (end < s.length()) { + c = s.charAt(end); + // Note that 'e' or 'E' are not valid path commands, but could be + // used for floating point numbers' scientific notation. + // Therefore, when searching for next command, we should ignore 'e' + // and 'E'. + if ((((c - 'A') * (c - 'Z') <= 0) || ((c - 'a') * (c - 'z') <= 0)) + && c != 'e' && c != 'E') { + return end; + } + end++; + } + return end; + } + + private static void addNode(ArrayList list, char cmd, float[] val) { + list.add(new PathDataNode(cmd, val)); + } + + private static class ExtractFloatResult { + // We need to return the position of the next separator and whether the + // next float starts with a '-' or a '.'. + int mEndPosition; + boolean mEndWithNegOrDot; + } + + /** + * Parse the floats in the string. + * This is an optimized version of parseFloat(s.split(",|\\s")); + * + * @param s the string containing a command and list of floats + * @return array of floats + */ + private static float[] getFloats(String s) { + if (s.charAt(0) == 'z' | s.charAt(0) == 'Z') { + return new float[0]; + } + try { + float[] results = new float[s.length()]; + int count = 0; + int startPosition = 1; + int endPosition = 0; + + ExtractFloatResult result = new ExtractFloatResult(); + int totalLength = s.length(); + + // The startPosition should always be the first character of the + // current number, and endPosition is the character after the current + // number. + while (startPosition < totalLength) { + extract(s, startPosition, result); + endPosition = result.mEndPosition; + + if (startPosition < endPosition) { + results[count++] = Float.parseFloat( + s.substring(startPosition, endPosition)); + } + + if (result.mEndWithNegOrDot) { + // Keep the '-' or '.' sign with next number. + startPosition = endPosition; + } else { + startPosition = endPosition + 1; + } + } + return copyOfRange(results, 0, count); + } catch (NumberFormatException e) { + throw new RuntimeException("error in parsing \"" + s + "\"", e); + } + } + + /** + * Calculate the position of the next comma or space or negative sign + * @param s the string to search + * @param start the position to start searching + * @param result the result of the extraction, including the position of the + * the starting position of next number, whether it is ending with a '-'. + */ + private static void extract(String s, int start, ExtractFloatResult result) { + // Now looking for ' ', ',', '.' or '-' from the start. + int currentIndex = start; + boolean foundSeparator = false; + result.mEndWithNegOrDot = false; + boolean secondDot = false; + boolean isExponential = false; + for (; currentIndex < s.length(); currentIndex++) { + boolean isPrevExponential = isExponential; + isExponential = false; + char currentChar = s.charAt(currentIndex); + switch (currentChar) { + case ' ': + case ',': + foundSeparator = true; + break; + case '-': + // The negative sign following a 'e' or 'E' is not a separator. + if (currentIndex != start && !isPrevExponential) { + foundSeparator = true; + result.mEndWithNegOrDot = true; + } + break; + case '.': + if (!secondDot) { + secondDot = true; + } else { + // This is the second dot, and it is considered as a separator. + foundSeparator = true; + result.mEndWithNegOrDot = true; + } + break; + case 'e': + case 'E': + isExponential = true; + break; + } + if (foundSeparator) { + break; + } + } + // When there is nothing found, then we put the end position to the end + // of the string. + result.mEndPosition = currentIndex; + } + + /** + * Each PathDataNode represents one command in the "d" attribute of the svg + * file. + * An array of PathDataNode can represent the whole "d" attribute. + */ + public static class PathDataNode { + private char mType; + private float[] mParams; + + private PathDataNode(char type, float[] params) { + mType = type; + mParams = params; + } + + private PathDataNode(PathDataNode n) { + mType = n.mType; + mParams = copyOfRange(n.mParams, 0, n.mParams.length); + } + + /** + * Convert an array of PathDataNode to Path. + * + * @param node The source array of PathDataNode. + * @param path The target Path object. + */ + public static void nodesToPath(PathDataNode[] node, Path path) { + float[] current = new float[6]; + char previousCommand = 'm'; + for (int i = 0; i < node.length; i++) { + addCommand(path, current, previousCommand, node[i].mType, node[i].mParams); + previousCommand = node[i].mType; + } + } + + /** + * The current PathDataNode will be interpolated between the + * nodeFrom and nodeTo according to the + * fraction. + * + * @param nodeFrom The start value as a PathDataNode. + * @param nodeTo The end value as a PathDataNode + * @param fraction The fraction to interpolate. + */ + public void interpolatePathDataNode(PathDataNode nodeFrom, + PathDataNode nodeTo, float fraction) { + for (int i = 0; i < nodeFrom.mParams.length; i++) { + mParams[i] = nodeFrom.mParams[i] * (1 - fraction) + + nodeTo.mParams[i] * fraction; + } + } + + private static void addCommand(Path path, float[] current, + char previousCmd, char cmd, float[] val) { + + int incr = 2; + float currentX = current[0]; + float currentY = current[1]; + float ctrlPointX = current[2]; + float ctrlPointY = current[3]; + float currentSegmentStartX = current[4]; + float currentSegmentStartY = current[5]; + float reflectiveCtrlPointX; + float reflectiveCtrlPointY; + + switch (cmd) { + case 'z': + case 'Z': + path.close(); + // Path is closed here, but we need to move the pen to the + // closed position. So we cache the segment's starting position, + // and restore it here. + currentX = currentSegmentStartX; + currentY = currentSegmentStartY; + ctrlPointX = currentSegmentStartX; + ctrlPointY = currentSegmentStartY; + path.moveTo(currentX, currentY); + break; + case 'm': + case 'M': + case 'l': + case 'L': + case 't': + case 'T': + incr = 2; + break; + case 'h': + case 'H': + case 'v': + case 'V': + incr = 1; + break; + case 'c': + case 'C': + incr = 6; + break; + case 's': + case 'S': + case 'q': + case 'Q': + incr = 4; + break; + case 'a': + case 'A': + incr = 7; + break; + } + + for (int k = 0; k < val.length; k += incr) { + switch (cmd) { + case 'm': // moveto - Start a new sub-path (relative) + path.rMoveTo(val[k + 0], val[k + 1]); + currentX += val[k + 0]; + currentY += val[k + 1]; + currentSegmentStartX = currentX; + currentSegmentStartY = currentY; + 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]; + currentSegmentStartX = currentX; + currentSegmentStartY = currentY; + 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 'h': // horizontal lineto - Draws a horizontal line (relative) + path.rLineTo(val[k + 0], 0); + currentX += val[k + 0]; + break; + case 'H': // horizontal lineto - Draws a horizontal line + path.lineTo(val[k + 0], currentY); + currentX = val[k + 0]; + break; + case 'v': // vertical lineto - Draws a vertical line from the current point (r) + path.rLineTo(0, val[k + 0]); + currentY += val[k + 0]; + break; + case 'V': // vertical lineto - Draws a vertical line from the current point + path.lineTo(currentX, val[k + 0]); + currentY = val[k + 0]; + break; + case 'c': // curveto - Draws a cubic Bézier curve (relative) + path.rCubicTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3], + val[k + 4], val[k + 5]); + + ctrlPointX = currentX + val[k + 2]; + ctrlPointY = currentY + val[k + 3]; + currentX += val[k + 4]; + currentY += val[k + 5]; + + break; + case 'C': // curveto - Draws a cubic Bézier curve + path.cubicTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3], + val[k + 4], val[k + 5]); + currentX = val[k + 4]; + currentY = val[k + 5]; + ctrlPointX = val[k + 2]; + ctrlPointY = val[k + 3]; + break; + case 's': // smooth curveto - Draws a cubic Bézier curve (reflective cp) + reflectiveCtrlPointX = 0; + reflectiveCtrlPointY = 0; + if (previousCmd == 'c' || previousCmd == 's' + || previousCmd == 'C' || previousCmd == 'S') { + reflectiveCtrlPointX = currentX - ctrlPointX; + reflectiveCtrlPointY = currentY - ctrlPointY; + } + path.rCubicTo(reflectiveCtrlPointX, reflectiveCtrlPointY, + val[k + 0], val[k + 1], + val[k + 2], val[k + 3]); + + ctrlPointX = currentX + val[k + 0]; + ctrlPointY = currentY + val[k + 1]; + currentX += val[k + 2]; + currentY += val[k + 3]; + break; + case 'S': // shorthand/smooth curveto Draws a cubic Bézier curve(reflective cp) + reflectiveCtrlPointX = currentX; + reflectiveCtrlPointY = currentY; + if (previousCmd == 'c' || previousCmd == 's' + || previousCmd == 'C' || previousCmd == 'S') { + reflectiveCtrlPointX = 2 * currentX - ctrlPointX; + reflectiveCtrlPointY = 2 * currentY - ctrlPointY; + } + path.cubicTo(reflectiveCtrlPointX, reflectiveCtrlPointY, + val[k + 0], val[k + 1], val[k + 2], val[k + 3]); + ctrlPointX = val[k + 0]; + ctrlPointY = val[k + 1]; + currentX = val[k + 2]; + currentY = val[k + 3]; + break; + case 'q': // Draws a quadratic Bézier (relative) + path.rQuadTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3]); + ctrlPointX = currentX + val[k + 0]; + ctrlPointY = currentY + val[k + 1]; + currentX += val[k + 2]; + currentY += val[k + 3]; + break; + case 'Q': // Draws a quadratic Bézier + path.quadTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3]); + ctrlPointX = val[k + 0]; + ctrlPointY = val[k + 1]; + currentX = val[k + 2]; + currentY = val[k + 3]; + break; + case 't': // Draws a quadratic Bézier curve(reflective control point)(relative) + reflectiveCtrlPointX = 0; + reflectiveCtrlPointY = 0; + if (previousCmd == 'q' || previousCmd == 't' + || previousCmd == 'Q' || previousCmd == 'T') { + reflectiveCtrlPointX = currentX - ctrlPointX; + reflectiveCtrlPointY = currentY - ctrlPointY; + } + path.rQuadTo(reflectiveCtrlPointX, reflectiveCtrlPointY, + val[k + 0], val[k + 1]); + ctrlPointX = currentX + reflectiveCtrlPointX; + ctrlPointY = currentY + reflectiveCtrlPointY; + currentX += val[k + 0]; + currentY += val[k + 1]; + break; + case 'T': // Draws a quadratic Bézier curve (reflective control point) + reflectiveCtrlPointX = currentX; + reflectiveCtrlPointY = currentY; + if (previousCmd == 'q' || previousCmd == 't' + || previousCmd == 'Q' || previousCmd == 'T') { + reflectiveCtrlPointX = 2 * currentX - ctrlPointX; + reflectiveCtrlPointY = 2 * currentY - ctrlPointY; + } + path.quadTo(reflectiveCtrlPointX, reflectiveCtrlPointY, + val[k + 0], val[k + 1]); + ctrlPointX = reflectiveCtrlPointX; + ctrlPointY = reflectiveCtrlPointY; + currentX = val[k + 0]; + currentY = val[k + 1]; + break; + case 'a': // Draws an elliptical arc + // (rx ry x-axis-rotation large-arc-flag sweep-flag x y) + drawArc(path, + currentX, + currentY, + val[k + 5] + currentX, + val[k + 6] + currentY, + val[k + 0], + val[k + 1], + val[k + 2], + val[k + 3] != 0, + val[k + 4] != 0); + currentX += val[k + 5]; + currentY += val[k + 6]; + ctrlPointX = currentX; + ctrlPointY = currentY; + break; + case 'A': // Draws an elliptical arc + drawArc(path, + currentX, + currentY, + val[k + 5], + val[k + 6], + val[k + 0], + val[k + 1], + val[k + 2], + val[k + 3] != 0, + val[k + 4] != 0); + currentX = val[k + 5]; + currentY = val[k + 6]; + ctrlPointX = currentX; + ctrlPointY = currentY; + break; + } + previousCmd = cmd; + } + current[0] = currentX; + current[1] = currentY; + current[2] = ctrlPointX; + current[3] = ctrlPointY; + current[4] = currentSegmentStartX; + current[5] = currentSegmentStartY; + } + + 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/graphics/drawable/static/src/android/support/graphics/drawable/VectorDrawableCompat.java b/graphics/drawable/static/src/android/support/graphics/drawable/VectorDrawableCompat.java new file mode 100644 index 0000000000..d33c20496c --- /dev/null +++ b/graphics/drawable/static/src/android/support/graphics/drawable/VectorDrawableCompat.java @@ -0,0 +1,1470 @@ +/* + * Copyright (C) 2015 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.support.graphics.drawable; + +import android.content.res.ColorStateList; +import android.content.res.Resources; +import android.content.res.Resources.Theme; +import android.content.res.TypedArray; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.ColorFilter; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.PathMeasure; +import android.graphics.PixelFormat; +import android.graphics.PorterDuff; +import android.graphics.PorterDuff.Mode; +import android.graphics.PorterDuffColorFilter; +import android.graphics.Rect; +import android.graphics.Region; +import android.graphics.drawable.Drawable; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.DrawableRes; +import android.support.v4.util.ArrayMap; +import android.util.AttributeSet; +import android.util.Log; +import android.util.Xml; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Stack; + +/** + * 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 the following elements: + *

    + *

    <vector>
    + *
    + *
    Used to define a vector drawable + *
    + *
    android:name
    + *
    Defines the name of this vector drawable.
    + *
    android:width
    + *
    Used to define the intrinsic width of the drawable. This support all the dimension units, + * normally specified with dp.
    + *
    android:height
    + *
    Used to define the intrinsic height the drawable. This support all the dimension units, + * normally specified with dp.
    + *
    android:viewportWidth
    + *
    Used to define the width of the viewport space. Viewport is basically the virtual canvas + * where the paths are drawn on.
    + *
    android:viewportHeight
    + *
    Used to define the height of the viewport space. Viewport is basically the virtual canvas + * where the paths are drawn on.
    + *
    android:tint
    + *
    The color to apply to the drawable as a tint. By default, no tint is applied.
    + *
    android:tintMode
    + *
    The Porter-Duff blending mode for the tint color. The default value is src_in.
    + *
    android:autoMirrored
    + *
    Indicates if the drawable needs to be mirrored when its layout direction is RTL + * (right-to-left).
    + *
    android:alpha
    + *
    The opacity of this drawable.
    + *
    + *
    + *
    + *
    + *
    <group>
    + *
    Defines a group of paths or subgroups, plus transformation information. The transformations + * are defined in the same coordinates as the viewport. And the transformations are applied in the + * order of scale, rotate then translate. + *
    + *
    android:name
    + *
    Defines the name of the group.
    + *
    android:rotation
    + *
    The degrees of rotation of the group.
    + *
    android:pivotX
    + *
    The X coordinate of the pivot for the scale and rotation of the group. This is defined in the + * viewport space.
    + *
    android:pivotY
    + *
    The Y coordinate of the pivot for the scale and rotation of the group. This is defined in the + * viewport space.
    + *
    android:scaleX
    + *
    The amount of scale on the X Coordinate.
    + *
    android:scaleY
    + *
    The amount of scale on the Y coordinate.
    + *
    android:translateX
    + *
    The amount of translation on the X coordinate. This is defined in the viewport space.
    + *
    android:translateY
    + *
    The amount of translation on the Y coordinate. This is defined in the viewport space.
    + *
    + *
    + *
    + *
    + *
    <path>
    + *
    Defines paths to be drawn. + *
    + *
    android:name
    + *
    Defines the name of the path.
    + *
    android:pathData
    + *
    Defines path data using exactly same format as "d" attribute in the SVG's path + * data. This is defined in the viewport space.
    + *
    android:fillColor
    + *
    Defines the color to fill the path (none if not present).
    + *
    android:strokeColor
    + *
    Defines the color to draw the path outline (none if not present).
    + *
    android:strokeWidth
    + *
    The width a path stroke.
    + *
    android:strokeAlpha
    + *
    The opacity of a path stroke.
    + *
    android:fillAlpha
    + *
    The opacity to fill the path with.
    + *
    android:trimPathStart
    + *
    The fraction of the path to trim from the start, in the range from 0 to 1.
    + *
    android:trimPathEnd
    + *
    The fraction of the path to trim from the end, in the range from 0 to 1.
    + *
    android:trimPathOffset
    + *
    Shift trim region (allows showed region to include the start and end), in the range from 0 to + * 1.
    + *
    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.
    + *
    + *
    + *
    + *
    + *
    <clip-path>
    + *
    Defines path to be the current clip. + *
    + *
    android:name
    + *
    Defines the name of the clip path.
    + *
    android:pathData
    + *
    Defines clip path data using the same format as "d" attribute in the SVG's + * path data.
    + *
    + *
    + *
    + *
  • Here is a simple VectorDrawable in this vectordrawable.xml file. + *
    + * <vector xmlns:android="http://schemas.android.com/apk/res/android"
    + *     android:height="64dp"
    + *     android:width="64dp"
    + *     android:viewportHeight="600"
    + *     android:viewportWidth="600" >
    + *     <group
    + *         android:name="rotationGroup"
    + *         android:pivotX="300.0"
    + *         android:pivotY="300.0"
    + *         android:rotation="45.0" >
    + *         <path
    + *             android:name="v"
    + *             android:fillColor="#000000"
    + *             android:pathData="M300,70 l 0,-70 70,70 0,0 -70,70z" />
    + *     </group>
    + * </vector>
    + * 
  • + */ +public class VectorDrawableCompat extends Drawable { + static final String LOGTAG = "VectorDrawableCompat"; + + static final PorterDuff.Mode DEFAULT_TINT_MODE = PorterDuff.Mode.SRC_IN; + + private static final String SHAPE_CLIP_PATH = "clip-path"; + private static final String SHAPE_GROUP = "group"; + private static final String SHAPE_PATH = "path"; + private static final String SHAPE_VECTOR = "vector"; + + private static final int LINECAP_BUTT = 0; + private static final int LINECAP_ROUND = 1; + private static final int LINECAP_SQUARE = 2; + + private static final int LINEJOIN_MITER = 0; + private static final int LINEJOIN_ROUND = 1; + private static final int LINEJOIN_BEVEL = 2; + + private static final boolean DBG_VECTOR_DRAWABLE = true; + + private VectorDrawableState mVectorState; + + private PorterDuffColorFilter mTintFilter; + private ColorFilter mColorFilter; + + private boolean mMutated; + + // AnimatedVectorDrawable needs to turn off the cache all the time, otherwise, + // caching the bitmap by default is allowed. + private boolean mAllowCaching = true; + + private VectorDrawableCompat() { + mVectorState = new VectorDrawableState(); + } + + private VectorDrawableCompat(VectorDrawableState state) { + mVectorState = state; + mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode); + } + + @Override + public Drawable mutate() { + if (!mMutated && super.mutate() == this) { + mVectorState = new VectorDrawableState(mVectorState); + mMutated = true; + } + return this; + } + + Object getTargetByName(String name) { + return mVectorState.mVPathRenderer.mVGTargetsMap.get(name); + } + + @Override + public ConstantState getConstantState() { + mVectorState.mChangingConfigurations = getChangingConfigurations(); + return mVectorState; + } + + @Override + public void draw(Canvas canvas) { + final Rect bounds = getBounds(); + if (bounds.width() == 0 || bounds.height() == 0) { + // too small to draw + return; + } + + final int saveCount = canvas.save(); + final boolean needMirroring = needMirroring(); + + canvas.translate(bounds.left, bounds.top); + if (needMirroring) { + canvas.translate(bounds.width(), 0); + canvas.scale(-1.0f, 1.0f); + } + + // Color filters always override tint filters. + final ColorFilter colorFilter = mColorFilter == null ? mTintFilter : mColorFilter; + + if (!mAllowCaching) { + // AnimatedVectorDrawable + if (!mVectorState.hasTranslucentRoot()) { + mVectorState.mVPathRenderer.draw( + canvas, bounds.width(), bounds.height(), colorFilter); + } else { + mVectorState.createCachedBitmapIfNeeded(bounds); + mVectorState.updateCachedBitmap(bounds); + mVectorState.drawCachedBitmapWithRootAlpha(canvas, colorFilter); + } + } else { + // Static Vector Drawable case. + mVectorState.createCachedBitmapIfNeeded(bounds); + if (!mVectorState.canReuseCache()) { + mVectorState.updateCachedBitmap(bounds); + mVectorState.updateCacheStates(); + } + mVectorState.drawCachedBitmapWithRootAlpha(canvas, colorFilter); + } + + canvas.restoreToCount(saveCount); + } + + public int getAlpha() { + return mVectorState.mVPathRenderer.getRootAlpha(); + } + + @Override + public void setAlpha(int alpha) { + if (mVectorState.mVPathRenderer.getRootAlpha() != alpha) { + mVectorState.mVPathRenderer.setRootAlpha(alpha); + invalidateSelf(); + } + } + + @Override + public void setColorFilter(ColorFilter colorFilter) { + mColorFilter = colorFilter; + invalidateSelf(); + } + + /** + * Ensures the tint filter is consistent with the current tint color and + * mode. + */ + PorterDuffColorFilter updateTintFilter(PorterDuffColorFilter tintFilter, ColorStateList tint, + PorterDuff.Mode tintMode) { + if (tint == null || tintMode == null) { + return null; + } + // setMode, setColor of PorterDuffColorFilter are not public method in SDK v7. + // Therefore we create a new one all the time here. Don't expect this is called often. + final int color = tint.getColorForState(getState(), Color.TRANSPARENT); + return new PorterDuffColorFilter(color, tintMode); + } + + public void setTint(int tint) { + setTintList(ColorStateList.valueOf(tint)); + } + + public void setTintList(ColorStateList tint) { + final VectorDrawableState state = mVectorState; + if (state.mTint != tint) { + state.mTint = tint; + mTintFilter = updateTintFilter(mTintFilter, tint, state.mTintMode); + invalidateSelf(); + } + } + + public void setTintMode(Mode tintMode) { + final VectorDrawableState state = mVectorState; + if (state.mTintMode != tintMode) { + state.mTintMode = tintMode; + mTintFilter = updateTintFilter(mTintFilter, state.mTint, tintMode); + invalidateSelf(); + } + } + + @Override + public boolean isStateful() { + return super.isStateful() || (mVectorState != null && mVectorState.mTint != null + && mVectorState.mTint.isStateful()); + } + + @Override + protected boolean onStateChange(int[] stateSet) { + final VectorDrawableState state = mVectorState; + if (state.mTint != null && state.mTintMode != null) { + // mTintFilter = updateTintFilter(this, mTintFilter, state.mTint, state.mTintMode); + invalidateSelf(); + return true; + } + return false; + } + + @Override + public int getOpacity() { + return PixelFormat.TRANSLUCENT; + } + + @Override + public int getIntrinsicWidth() { + return (int) mVectorState.mVPathRenderer.mBaseWidth; + } + + @Override + public int getIntrinsicHeight() { + return (int) mVectorState.mVPathRenderer.mBaseHeight; + } + + // Don't support re-applying themes. The initial theme loading is working. + public boolean canApplyTheme() { + return false; + } + + /** + * The size of a pixel when scaled from the intrinsic dimension to the viewport dimension. This + * is used to calculate the path animation accuracy. + * + * @hide + */ + public float getPixelSize() { + if (mVectorState == null && mVectorState.mVPathRenderer == null || + mVectorState.mVPathRenderer.mBaseWidth == 0 || + mVectorState.mVPathRenderer.mBaseHeight == 0 || + mVectorState.mVPathRenderer.mViewportHeight == 0 || + mVectorState.mVPathRenderer.mViewportWidth == 0) { + return 1; // fall back to 1:1 pixel mapping. + } + float intrinsicWidth = mVectorState.mVPathRenderer.mBaseWidth; + float intrinsicHeight = mVectorState.mVPathRenderer.mBaseHeight; + float viewportWidth = mVectorState.mVPathRenderer.mViewportWidth; + float viewportHeight = mVectorState.mVPathRenderer.mViewportHeight; + float scaleX = viewportWidth / intrinsicWidth; + float scaleY = viewportHeight / intrinsicHeight; + return Math.min(scaleX, scaleY); + } + + /** + * Create a VectorDrawableCompat object. + * + * @param res the resources. + * @param resId the resource ID for VectorDrawableCompat object. + * @param theme the theme of this vector drawable, it can be null. + * @return a new VectorDrawableCompat or null if parsing error is found. + */ + @Nullable + public static VectorDrawableCompat create(@NonNull Resources res, @DrawableRes int resId, + @Nullable Theme theme) { + try { + final XmlPullParser parser = res.getXml(resId); + final AttributeSet attrs = Xml.asAttributeSet(parser); + int type; + while ((type = parser.next()) != XmlPullParser.START_TAG && + type != XmlPullParser.END_DOCUMENT) { + // Empty loop + } + if (type != XmlPullParser.START_TAG) { + throw new XmlPullParserException("No start tag found"); + } + + final VectorDrawableCompat drawable = new VectorDrawableCompat(); + drawable.inflate(res, parser, attrs, theme); + + return drawable; + } catch (XmlPullParserException e) { + Log.e(LOGTAG, "parser error", e); + } catch (IOException e) { + Log.e(LOGTAG, "parser error", e); + } + return null; + } + + private static int applyAlpha(int color, float alpha) { + int alphaBytes = Color.alpha(color); + color &= 0x00FFFFFF; + color |= ((int) (alphaBytes * alpha)) << 24; + return color; + } + + /** + * Obtains styled attributes from the theme, if available, or unstyled + * resources if the theme is null. + */ + static TypedArray obtainAttributes( + Resources res, Theme theme, AttributeSet set, int[] attrs) { + if (theme == null) { + return res.obtainAttributes(set, attrs); + } + return theme.obtainStyledAttributes(set, attrs, 0, 0); + } + + + @Override + public void inflate(Resources res, XmlPullParser parser, AttributeSet attrs) + throws XmlPullParserException, IOException { + inflate(res, parser, attrs, null); + } + + public void inflate(Resources res, XmlPullParser parser, AttributeSet attrs, Theme theme) + throws XmlPullParserException, IOException { + final VectorDrawableState state = mVectorState; + final VPathRenderer pathRenderer = new VPathRenderer(); + state.mVPathRenderer = pathRenderer; + + final TypedArray a = obtainAttributes(res, theme, attrs, R.styleable.VectorDrawable); + updateStateFromTypedArray(a); + a.recycle(); + state.mChangingConfigurations = getChangingConfigurations(); + state.mCacheDirty = true; + inflateInternal(res, parser, attrs, theme); + + mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode); + } + + private void updateStateFromTypedArray(TypedArray a) throws XmlPullParserException { + final VectorDrawableState state = mVectorState; + final VPathRenderer pathRenderer = state.mVPathRenderer; + + // Account for any configuration changes. + // state.mChangingConfigurations |= Utils.getChangingConfigurations(a); + + final int tintMode = a.getInt(R.styleable.VectorDrawable_tintMode, -1); + // if (tintMode != -1) { + // state.mTintMode = Utils.parseTintMode(tintMode, DEFAULT_TINT_MODE); + // } + + final ColorStateList tint = a.getColorStateList(R.styleable.VectorDrawable_tint); + if (tint != null) { + state.mTint = tint; + } + + state.mAutoMirrored = a.getBoolean( + R.styleable.VectorDrawable_autoMirrored, state.mAutoMirrored); + + pathRenderer.mViewportWidth = a.getFloat( + R.styleable.VectorDrawable_viewportWidth, pathRenderer.mViewportWidth); + pathRenderer.mViewportHeight = a.getFloat( + R.styleable.VectorDrawable_viewportHeight, pathRenderer.mViewportHeight); + + if (pathRenderer.mViewportWidth <= 0) { + throw new XmlPullParserException(a.getPositionDescription() + + " tag requires viewportWidth > 0"); + } else if (pathRenderer.mViewportHeight <= 0) { + throw new XmlPullParserException(a.getPositionDescription() + + " tag requires viewportHeight > 0"); + } + + pathRenderer.mBaseWidth = a.getDimension( + R.styleable.VectorDrawable_width, pathRenderer.mBaseWidth); + pathRenderer.mBaseHeight = a.getDimension( + R.styleable.VectorDrawable_height, pathRenderer.mBaseHeight); + + if (pathRenderer.mBaseWidth <= 0) { + throw new XmlPullParserException(a.getPositionDescription() + + " tag requires width > 0"); + } else if (pathRenderer.mBaseHeight <= 0) { + throw new XmlPullParserException(a.getPositionDescription() + + " tag requires height > 0"); + } + + final float alphaInFloat = a.getFloat(R.styleable.VectorDrawable_alpha, + pathRenderer.getAlpha()); + pathRenderer.setAlpha(alphaInFloat); + + final String name = a.getString(R.styleable.VectorDrawable_name); + if (name != null) { + pathRenderer.mRootName = name; + pathRenderer.mVGTargetsMap.put(name, pathRenderer); + } + } + + private void inflateInternal(Resources res, XmlPullParser parser, AttributeSet attrs, + Theme theme) throws XmlPullParserException, IOException { + final VectorDrawableState state = mVectorState; + final VPathRenderer pathRenderer = state.mVPathRenderer; + boolean noPathTag = true; + + // Use a stack to help to build the group tree. + // The top of the stack is always the current group. + final Stack groupStack = new Stack(); + groupStack.push(pathRenderer.mRootGroup); + + int eventType = parser.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + final String tagName = parser.getName(); + final VGroup currentGroup = groupStack.peek(); + Log.v(LOGTAG, tagName); + if (SHAPE_PATH.equals(tagName)) { + final VFullPath path = new VFullPath(); + path.inflate(res, attrs, theme); + currentGroup.mChildren.add(path); + if (path.getPathName() != null) { + pathRenderer.mVGTargetsMap.put(path.getPathName(), path); + } + noPathTag = false; + state.mChangingConfigurations |= path.mChangingConfigurations; + } else if (SHAPE_CLIP_PATH.equals(tagName)) { + final VClipPath path = new VClipPath(); + path.inflate(res, attrs, theme); + currentGroup.mChildren.add(path); + if (path.getPathName() != null) { + pathRenderer.mVGTargetsMap.put(path.getPathName(), path); + } + state.mChangingConfigurations |= path.mChangingConfigurations; + } else if (SHAPE_GROUP.equals(tagName)) { + VGroup newChildGroup = new VGroup(); + newChildGroup.inflate(res, attrs, theme); + currentGroup.mChildren.add(newChildGroup); + groupStack.push(newChildGroup); + if (newChildGroup.getGroupName() != null) { + pathRenderer.mVGTargetsMap.put(newChildGroup.getGroupName(), + newChildGroup); + } + state.mChangingConfigurations |= newChildGroup.mChangingConfigurations; + } + } else if (eventType == XmlPullParser.END_TAG) { + final String tagName = parser.getName(); + if (SHAPE_GROUP.equals(tagName)) { + groupStack.pop(); + } + } + eventType = parser.next(); + } + + // Print the tree out for debug. + if (DBG_VECTOR_DRAWABLE) { + printGroupTree(pathRenderer.mRootGroup, 0); + } + + if (noPathTag) { + final StringBuffer tag = new StringBuffer(); + + if (tag.length() > 0) { + tag.append(" or "); + } + tag.append(SHAPE_PATH); + + throw new XmlPullParserException("no " + tag + " defined"); + } + } + + private void printGroupTree(VGroup currentGroup, int level) { + String indent = ""; + for (int i = 0; i < level; i++) { + indent += " "; + } + // Print the current node + Log.v(LOGTAG, indent + "current group is :" + currentGroup.getGroupName() + + " rotation is " + currentGroup.mRotate); + Log.v(LOGTAG, indent + "matrix is :" + currentGroup.getLocalMatrix().toString()); + // Then print all the children groups + for (int i = 0; i < currentGroup.mChildren.size(); i++) { + Object child = currentGroup.mChildren.get(i); + if (child instanceof VGroup) { + printGroupTree((VGroup) child, level + 1); + } + } + } + + void setAllowCaching(boolean allowCaching) { + mAllowCaching = allowCaching; + } + + // We don't support RTL auto mirroring since the getLayoutDirection() is for API 17+. + private boolean needMirroring() { + return false; + } + + private static class VectorDrawableState extends ConstantState { + int mChangingConfigurations; + VPathRenderer mVPathRenderer; + ColorStateList mTint = null; + Mode mTintMode = DEFAULT_TINT_MODE; + boolean mAutoMirrored; + + Bitmap mCachedBitmap; + int[] mCachedThemeAttrs; + ColorStateList mCachedTint; + Mode mCachedTintMode; + int mCachedRootAlpha; + boolean mCachedAutoMirrored; + boolean mCacheDirty; + + /** Temporary paint object used to draw cached bitmaps. */ + Paint mTempPaint; + + // Deep copy for mutate() or implicitly mutate. + public VectorDrawableState(VectorDrawableState copy) { + if (copy != null) { + mChangingConfigurations = copy.mChangingConfigurations; + mVPathRenderer = new VPathRenderer(copy.mVPathRenderer); + if (copy.mVPathRenderer.mFillPaint != null) { + mVPathRenderer.mFillPaint = new Paint(copy.mVPathRenderer.mFillPaint); + } + if (copy.mVPathRenderer.mStrokePaint != null) { + mVPathRenderer.mStrokePaint = new Paint(copy.mVPathRenderer.mStrokePaint); + } + mTint = copy.mTint; + mTintMode = copy.mTintMode; + mAutoMirrored = copy.mAutoMirrored; + } + } + + public void drawCachedBitmapWithRootAlpha(Canvas canvas, ColorFilter filter) { + // The bitmap's size is the same as the bounds. + final Paint p = getPaint(filter); + canvas.drawBitmap(mCachedBitmap, 0, 0, p); + } + + public boolean hasTranslucentRoot() { + return mVPathRenderer.getRootAlpha() < 255; + } + + /** + * @return null when there is no need for alpha paint. + */ + public Paint getPaint(ColorFilter filter) { + if (!hasTranslucentRoot() && filter == null) { + return null; + } + + if (mTempPaint == null) { + mTempPaint = new Paint(); + mTempPaint.setFilterBitmap(true); + } + mTempPaint.setAlpha(mVPathRenderer.getRootAlpha()); + mTempPaint.setColorFilter(filter); + return mTempPaint; + } + + public void updateCachedBitmap(Rect bounds) { + mCachedBitmap.eraseColor(Color.TRANSPARENT); + Canvas tmpCanvas = new Canvas(mCachedBitmap); + mVPathRenderer.draw(tmpCanvas, bounds.width(), bounds.height(), null); + } + + public void createCachedBitmapIfNeeded(Rect bounds) { + if (mCachedBitmap == null || !canReuseBitmap(bounds.width(), + bounds.height())) { + mCachedBitmap = Bitmap.createBitmap(bounds.width(), bounds.height(), + Bitmap.Config.ARGB_8888); + mCacheDirty = true; + } + + } + + public boolean canReuseBitmap(int width, int height) { + if (width == mCachedBitmap.getWidth() + && height == mCachedBitmap.getHeight()) { + return true; + } + return false; + } + + public boolean canReuseCache() { + if (!mCacheDirty + && mCachedTint == mTint + && mCachedTintMode == mTintMode + && mCachedAutoMirrored == mAutoMirrored + && mCachedRootAlpha == mVPathRenderer.getRootAlpha()) { + return true; + } + return false; + } + + public void updateCacheStates() { + // Use shallow copy here and shallow comparison in canReuseCache(), + // likely hit cache miss more, but practically not much difference. + mCachedTint = mTint; + mCachedTintMode = mTintMode; + mCachedRootAlpha = mVPathRenderer.getRootAlpha(); + mCachedAutoMirrored = mAutoMirrored; + mCacheDirty = false; + } + + public VectorDrawableState() { + mVPathRenderer = new VPathRenderer(); + } + + @Override + public Drawable newDrawable() { + return new VectorDrawableCompat(this); + } + + @Override + public Drawable newDrawable(Resources res) { + return new VectorDrawableCompat(this); + } + + @Override + public int getChangingConfigurations() { + return mChangingConfigurations; + } + } + + private static class VPathRenderer { + /* Right now the internal data structure is organized as a tree. + * Each node can be a group node, or a path. + * A group node can have groups or paths as children, but a path node has + * no children. + * One example can be: + * Root Group + * / | \ + * Group Path Group + * / \ | + * Path Path Path + * + */ + // Variables that only used temporarily inside the draw() call, so there + // is no need for deep copying. + private final Path mPath; + private final Path mRenderPath; + private static final Matrix IDENTITY_MATRIX = new Matrix(); + private final Matrix mFinalPathMatrix = new Matrix(); + + private Paint mStrokePaint; + private Paint mFillPaint; + private PathMeasure mPathMeasure; + + ///////////////////////////////////////////////////// + // Variables below need to be copied (deep copy if applicable) for mutation. + private int mChangingConfigurations; + private final VGroup mRootGroup; + float mBaseWidth = 0; + float mBaseHeight = 0; + float mViewportWidth = 0; + float mViewportHeight = 0; + int mRootAlpha = 0xFF; + String mRootName = null; + + final ArrayMap mVGTargetsMap = new ArrayMap(); + + public VPathRenderer() { + mRootGroup = new VGroup(); + mPath = new Path(); + mRenderPath = new Path(); + } + + public void setRootAlpha(int alpha) { + mRootAlpha = alpha; + } + + public int getRootAlpha() { + return mRootAlpha; + } + + // setAlpha() and getAlpha() are used mostly for animation purpose, since + // Animator like to use alpha from 0 to 1. + public void setAlpha(float alpha) { + setRootAlpha((int) (alpha * 255)); + } + + @SuppressWarnings("unused") + public float getAlpha() { + return getRootAlpha() / 255.0f; + } + + public VPathRenderer(VPathRenderer copy) { + mRootGroup = new VGroup(copy.mRootGroup, mVGTargetsMap); + mPath = new Path(copy.mPath); + mRenderPath = new Path(copy.mRenderPath); + mBaseWidth = copy.mBaseWidth; + mBaseHeight = copy.mBaseHeight; + mViewportWidth = copy.mViewportWidth; + mViewportHeight = copy.mViewportHeight; + mChangingConfigurations = copy.mChangingConfigurations; + mRootAlpha = copy.mRootAlpha; + mRootName = copy.mRootName; + if (copy.mRootName != null) { + mVGTargetsMap.put(copy.mRootName, this); + } + } + + private void drawGroupTree(VGroup currentGroup, Matrix currentMatrix, + Canvas canvas, int w, int h, ColorFilter filter) { + // Calculate current group's matrix by preConcat the parent's and + // and the current one on the top of the stack. + // Basically the Mfinal = Mviewport * M0 * M1 * M2; + // Mi the local matrix at level i of the group tree. + currentGroup.mStackedMatrix.set(currentMatrix); + + currentGroup.mStackedMatrix.preConcat(currentGroup.mLocalMatrix); + + // Draw the group tree in the same order as the XML file. + for (int i = 0; i < currentGroup.mChildren.size(); i++) { + Object child = currentGroup.mChildren.get(i); + if (child instanceof VGroup) { + VGroup childGroup = (VGroup) child; + drawGroupTree(childGroup, currentGroup.mStackedMatrix, + canvas, w, h, filter); + } else if (child instanceof VPath) { + VPath childPath = (VPath) child; + drawPath(currentGroup, childPath, canvas, w, h, filter); + } + } + } + + public void draw(Canvas canvas, int w, int h, ColorFilter filter) { + // Travese the tree in pre-order to draw. + drawGroupTree(mRootGroup, IDENTITY_MATRIX, canvas, w, h, filter); + } + + private void drawPath(VGroup vGroup, VPath vPath, Canvas canvas, int w, int h, + ColorFilter filter) { + final float scaleX = w / mViewportWidth; + final float scaleY = h / mViewportHeight; + final float minScale = Math.min(scaleX, scaleY); + + mFinalPathMatrix.set(vGroup.mStackedMatrix); + mFinalPathMatrix.postScale(scaleX, scaleY); + + vPath.toPath(mPath); + final Path path = mPath; + + mRenderPath.reset(); + + if (vPath.isClipPath()) { + mRenderPath.addPath(path, mFinalPathMatrix); + canvas.clipPath(mRenderPath, Region.Op.REPLACE); + } else { + VFullPath fullPath = (VFullPath) vPath; + if (fullPath.mTrimPathStart != 0.0f || fullPath.mTrimPathEnd != 1.0f) { + float start = (fullPath.mTrimPathStart + fullPath.mTrimPathOffset) % 1.0f; + float end = (fullPath.mTrimPathEnd + fullPath.mTrimPathOffset) % 1.0f; + + if (mPathMeasure == null) { + mPathMeasure = new PathMeasure(); + } + mPathMeasure.setPath(mPath, false); + + float len = mPathMeasure.getLength(); + start = start * len; + end = end * len; + path.reset(); + if (start > end) { + mPathMeasure.getSegment(start, len, path, true); + mPathMeasure.getSegment(0f, end, path, true); + } else { + mPathMeasure.getSegment(start, end, path, true); + } + path.rLineTo(0, 0); // fix bug in measure + } + mRenderPath.addPath(path, mFinalPathMatrix); + + if (fullPath.mFillColor != Color.TRANSPARENT) { + if (mFillPaint == null) { + mFillPaint = new Paint(); + mFillPaint.setStyle(Paint.Style.FILL); + mFillPaint.setAntiAlias(true); + } + + final Paint fillPaint = mFillPaint; + fillPaint.setColor(applyAlpha(fullPath.mFillColor, fullPath.mFillAlpha)); + fillPaint.setColorFilter(filter); + canvas.drawPath(mRenderPath, fillPaint); + } + + if (fullPath.mStrokeColor != Color.TRANSPARENT) { + if (mStrokePaint == null) { + mStrokePaint = new Paint(); + mStrokePaint.setStyle(Paint.Style.STROKE); + mStrokePaint.setAntiAlias(true); + } + + final Paint strokePaint = mStrokePaint; + if (fullPath.mStrokeLineJoin != null) { + strokePaint.setStrokeJoin(fullPath.mStrokeLineJoin); + } + + if (fullPath.mStrokeLineCap != null) { + strokePaint.setStrokeCap(fullPath.mStrokeLineCap); + } + + strokePaint.setStrokeMiter(fullPath.mStrokeMiterlimit); + strokePaint.setColor(applyAlpha(fullPath.mStrokeColor, fullPath.mStrokeAlpha)); + strokePaint.setColorFilter(filter); + strokePaint.setStrokeWidth(fullPath.mStrokeWidth * minScale); + canvas.drawPath(mRenderPath, strokePaint); + } + } + } + } + + private static class VGroup { + // mStackedMatrix is only used temporarily when drawing, it combines all + // the parents' local matrices with the current one. + private final Matrix mStackedMatrix = new Matrix(); + + ///////////////////////////////////////////////////// + // Variables below need to be copied (deep copy if applicable) for mutation. + final ArrayList mChildren = new ArrayList(); + + private float mRotate = 0; + private float mPivotX = 0; + private float mPivotY = 0; + private float mScaleX = 1; + private float mScaleY = 1; + private float mTranslateX = 0; + private float mTranslateY = 0; + + // mLocalMatrix is updated based on the update of transformation information, + // either parsed from the XML or by animation. + private final Matrix mLocalMatrix = new Matrix(); + private int mChangingConfigurations; + private int[] mThemeAttrs; + private String mGroupName = null; + + public VGroup(VGroup copy, ArrayMap targetsMap) { + mRotate = copy.mRotate; + mPivotX = copy.mPivotX; + mPivotY = copy.mPivotY; + mScaleX = copy.mScaleX; + mScaleY = copy.mScaleY; + mTranslateX = copy.mTranslateX; + mTranslateY = copy.mTranslateY; + mThemeAttrs = copy.mThemeAttrs; + mGroupName = copy.mGroupName; + mChangingConfigurations = copy.mChangingConfigurations; + if (mGroupName != null) { + targetsMap.put(mGroupName, this); + } + + mLocalMatrix.set(copy.mLocalMatrix); + + final ArrayList children = copy.mChildren; + for (int i = 0; i < children.size(); i++) { + Object copyChild = children.get(i); + if (copyChild instanceof VGroup) { + VGroup copyGroup = (VGroup) copyChild; + mChildren.add(new VGroup(copyGroup, targetsMap)); + } else { + VPath newPath = null; + if (copyChild instanceof VFullPath) { + newPath = new VFullPath((VFullPath) copyChild); + } else if (copyChild instanceof VClipPath) { + newPath = new VClipPath((VClipPath) copyChild); + } else { + throw new IllegalStateException("Unknown object in the tree!"); + } + mChildren.add(newPath); + if (newPath.mPathName != null) { + targetsMap.put(newPath.mPathName, newPath); + } + } + } + } + + public VGroup() { + } + + public String getGroupName() { + return mGroupName; + } + + public Matrix getLocalMatrix() { + return mLocalMatrix; + } + + public void inflate(Resources res, AttributeSet attrs, Theme theme) { + final TypedArray a = obtainAttributes(res, theme, attrs, + R.styleable.VectorDrawableGroup); + updateStateFromTypedArray(a); + a.recycle(); + } + + private void updateStateFromTypedArray(TypedArray a) { + // Account for any configuration changes. + // mChangingConfigurations |= Utils.getChangingConfigurations(a); + + // Extract the theme attributes, if any. + mThemeAttrs = null; // TODO TINT THEME Not supported yet a.extractThemeAttrs(); + + mRotate = a.getFloat(R.styleable.VectorDrawableGroup_rotation, mRotate); + mPivotX = a.getFloat(R.styleable.VectorDrawableGroup_pivotX, mPivotX); + mPivotY = a.getFloat(R.styleable.VectorDrawableGroup_pivotY, mPivotY); + mScaleX = a.getFloat(R.styleable.VectorDrawableGroup_scaleX, mScaleX); + mScaleY = a.getFloat(R.styleable.VectorDrawableGroup_scaleY, mScaleY); + mTranslateX = a.getFloat(R.styleable.VectorDrawableGroup_translateX, mTranslateX); + mTranslateY = a.getFloat(R.styleable.VectorDrawableGroup_translateY, mTranslateY); + + final String groupName = a.getString(R.styleable.VectorDrawableGroup_name); + if (groupName != null) { + mGroupName = groupName; + } + + updateLocalMatrix(); + } + + private void updateLocalMatrix() { + // The order we apply is the same as the + // RenderNode.cpp::applyViewPropertyTransforms(). + mLocalMatrix.reset(); + mLocalMatrix.postTranslate(-mPivotX, -mPivotY); + mLocalMatrix.postScale(mScaleX, mScaleY); + mLocalMatrix.postRotate(mRotate, 0, 0); + mLocalMatrix.postTranslate(mTranslateX + mPivotX, mTranslateY + mPivotY); + } + + /* Setters and Getters, used by animator from AnimatedVectorDrawable. */ + @SuppressWarnings("unused") + public float getRotation() { + return mRotate; + } + + @SuppressWarnings("unused") + public void setRotation(float rotation) { + if (rotation != mRotate) { + mRotate = rotation; + updateLocalMatrix(); + } + } + + @SuppressWarnings("unused") + public float getPivotX() { + return mPivotX; + } + + @SuppressWarnings("unused") + public void setPivotX(float pivotX) { + if (pivotX != mPivotX) { + mPivotX = pivotX; + updateLocalMatrix(); + } + } + + @SuppressWarnings("unused") + public float getPivotY() { + return mPivotY; + } + + @SuppressWarnings("unused") + public void setPivotY(float pivotY) { + if (pivotY != mPivotY) { + mPivotY = pivotY; + updateLocalMatrix(); + } + } + + @SuppressWarnings("unused") + public float getScaleX() { + return mScaleX; + } + + @SuppressWarnings("unused") + public void setScaleX(float scaleX) { + if (scaleX != mScaleX) { + mScaleX = scaleX; + updateLocalMatrix(); + } + } + + @SuppressWarnings("unused") + public float getScaleY() { + return mScaleY; + } + + @SuppressWarnings("unused") + public void setScaleY(float scaleY) { + if (scaleY != mScaleY) { + mScaleY = scaleY; + updateLocalMatrix(); + } + } + + @SuppressWarnings("unused") + public float getTranslateX() { + return mTranslateX; + } + + @SuppressWarnings("unused") + public void setTranslateX(float translateX) { + if (translateX != mTranslateX) { + mTranslateX = translateX; + updateLocalMatrix(); + } + } + + @SuppressWarnings("unused") + public float getTranslateY() { + return mTranslateY; + } + + @SuppressWarnings("unused") + public void setTranslateY(float translateY) { + if (translateY != mTranslateY) { + mTranslateY = translateY; + updateLocalMatrix(); + } + } + } + + /** + * Common Path information for clip path and normal path. + */ + private static class VPath { + protected PathParser.PathDataNode[] mNodes = null; + String mPathName; + int mChangingConfigurations; + + public VPath() { + // Empty constructor. + } + + public VPath(VPath copy) { + mPathName = copy.mPathName; + mChangingConfigurations = copy.mChangingConfigurations; + mNodes = PathParser.deepCopyNodes(copy.mNodes); + } + + public void toPath(Path path) { + path.reset(); + if (mNodes != null) { + PathParser.PathDataNode.nodesToPath(mNodes, path); + } + } + + public String getPathName() { + return mPathName; + } + + public boolean canApplyTheme() { + return false; + } + + public void applyTheme(Theme t) { + } + + public boolean isClipPath() { + return false; + } + + /* Setters and Getters, used by animator from AnimatedVectorDrawable. */ + @SuppressWarnings("unused") + public PathParser.PathDataNode[] getPathData() { + return mNodes; + } + + @SuppressWarnings("unused") + public void setPathData(PathParser.PathDataNode[] nodes) { + if (!PathParser.canMorph(mNodes, nodes)) { + // This should not happen in the middle of animation. + mNodes = PathParser.deepCopyNodes(nodes); + } else { + PathParser.updateNodes(mNodes, nodes); + } + } + } + + /** + * Clip path, which only has name and pathData. + */ + private static class VClipPath extends VPath { + public VClipPath() { + // Empty constructor. + } + + public VClipPath(VClipPath copy) { + super(copy); + } + + public void inflate(Resources r, AttributeSet attrs, Theme theme) { + // TODO TINT THEME Not supported yet + final TypedArray a = obtainAttributes(r, theme, attrs, + R.styleable.VectorDrawableClipPath); + updateStateFromTypedArray(a); + a.recycle(); + } + + private void updateStateFromTypedArray(TypedArray a) { + // Account for any configuration changes. + // mChangingConfigurations |= Utils.getChangingConfigurations(a);; + + final String pathName = a.getString(R.styleable.VectorDrawableClipPath_name); + if (pathName != null) { + mPathName = pathName; + } + + final String pathData = a.getString(R.styleable.VectorDrawableClipPath_pathData); + if (pathData != null) { + mNodes = PathParser.createNodesFromPathData(pathData); + } + } + + @Override + public boolean isClipPath() { + return true; + } + } + + /** + * Normal path, which contains all the fill / paint information. + */ + private static class VFullPath extends VPath { + ///////////////////////////////////////////////////// + // Variables below need to be copied (deep copy if applicable) for mutation. + private int[] mThemeAttrs; + + int mStrokeColor = Color.TRANSPARENT; + float mStrokeWidth = 0; + + int mFillColor = Color.TRANSPARENT; + float mStrokeAlpha = 1.0f; + int mFillRule; + float mFillAlpha = 1.0f; + float mTrimPathStart = 0; + float mTrimPathEnd = 1; + float mTrimPathOffset = 0; + + Paint.Cap mStrokeLineCap = Paint.Cap.BUTT; + Paint.Join mStrokeLineJoin = Paint.Join.MITER; + float mStrokeMiterlimit = 4; + + public VFullPath() { + // Empty constructor. + } + + public VFullPath(VFullPath copy) { + super(copy); + mThemeAttrs = copy.mThemeAttrs; + + mStrokeColor = copy.mStrokeColor; + mStrokeWidth = copy.mStrokeWidth; + mStrokeAlpha = copy.mStrokeAlpha; + mFillColor = copy.mFillColor; + mFillRule = copy.mFillRule; + mFillAlpha = copy.mFillAlpha; + mTrimPathStart = copy.mTrimPathStart; + mTrimPathEnd = copy.mTrimPathEnd; + mTrimPathOffset = copy.mTrimPathOffset; + + mStrokeLineCap = copy.mStrokeLineCap; + mStrokeLineJoin = copy.mStrokeLineJoin; + mStrokeMiterlimit = copy.mStrokeMiterlimit; + } + + private Paint.Cap getStrokeLineCap(int id, Paint.Cap defValue) { + switch (id) { + case LINECAP_BUTT: + return Paint.Cap.BUTT; + case LINECAP_ROUND: + return Paint.Cap.ROUND; + case LINECAP_SQUARE: + return Paint.Cap.SQUARE; + default: + return defValue; + } + } + + private Paint.Join getStrokeLineJoin(int id, Paint.Join defValue) { + switch (id) { + case LINEJOIN_MITER: + return Paint.Join.MITER; + case LINEJOIN_ROUND: + return Paint.Join.ROUND; + case LINEJOIN_BEVEL: + return Paint.Join.BEVEL; + default: + return defValue; + } + } + + @Override + public boolean canApplyTheme() { + return mThemeAttrs != null; + } + + public void inflate(Resources r, AttributeSet attrs, Theme theme) { + final TypedArray a = obtainAttributes(r, theme, attrs, + R.styleable.VectorDrawablePath); + updateStateFromTypedArray(a); + a.recycle(); + } + + private void updateStateFromTypedArray(TypedArray a) { + // Account for any configuration changes. + // mChangingConfigurations |= Utils.getChangingConfigurations(a); + + // Extract the theme attributes, if any. + mThemeAttrs = null; // TODO TINT THEME Not supported yet a.extractThemeAttrs(); + + final String pathName = a.getString(R.styleable.VectorDrawablePath_name); + if (pathName != null) { + mPathName = pathName; + } + + final String pathData = a.getString(R.styleable.VectorDrawablePath_pathData); + if (pathData != null) { + mNodes = PathParser.createNodesFromPathData(pathData); + } + + mFillColor = a.getColor(R.styleable.VectorDrawablePath_fillColor, + mFillColor); + mFillAlpha = a.getFloat(R.styleable.VectorDrawablePath_fillAlpha, + mFillAlpha); + mStrokeLineCap = getStrokeLineCap(a.getInt( + R.styleable.VectorDrawablePath_strokeLineCap, -1), mStrokeLineCap); + mStrokeLineJoin = getStrokeLineJoin(a.getInt( + R.styleable.VectorDrawablePath_strokeLineJoin, -1), mStrokeLineJoin); + mStrokeMiterlimit = a.getFloat( + R.styleable.VectorDrawablePath_strokeMiterLimit, mStrokeMiterlimit); + mStrokeColor = a.getColor(R.styleable.VectorDrawablePath_strokeColor, + mStrokeColor); + mStrokeAlpha = a.getFloat(R.styleable.VectorDrawablePath_strokeAlpha, + mStrokeAlpha); + mStrokeWidth = a.getFloat(R.styleable.VectorDrawablePath_strokeWidth, + mStrokeWidth); + mTrimPathEnd = a.getFloat(R.styleable.VectorDrawablePath_trimPathEnd, + mTrimPathEnd); + mTrimPathOffset = a.getFloat( + R.styleable.VectorDrawablePath_trimPathOffset, mTrimPathOffset); + mTrimPathStart = a.getFloat( + R.styleable.VectorDrawablePath_trimPathStart, mTrimPathStart); + } + + @Override + public void applyTheme(Theme t) { + if (mThemeAttrs == null) { + return; + } + + /* + * TODO TINT THEME Not supported yet final TypedArray a = + * t.resolveAttributes(mThemeAttrs, R.styleable.VectorDrawablePath); + * updateStateFromTypedArray(a); a.recycle(); + */ + } + + /* Setters and Getters, used by animator from AnimatedVectorDrawable. */ + @SuppressWarnings("unused") + int getStrokeColor() { + return mStrokeColor; + } + + @SuppressWarnings("unused") + void setStrokeColor(int strokeColor) { + mStrokeColor = strokeColor; + } + + @SuppressWarnings("unused") + float getStrokeWidth() { + return mStrokeWidth; + } + + @SuppressWarnings("unused") + void setStrokeWidth(float strokeWidth) { + mStrokeWidth = strokeWidth; + } + + @SuppressWarnings("unused") + float getStrokeAlpha() { + return mStrokeAlpha; + } + + @SuppressWarnings("unused") + void setStrokeAlpha(float strokeAlpha) { + mStrokeAlpha = strokeAlpha; + } + + @SuppressWarnings("unused") + int getFillColor() { + return mFillColor; + } + + @SuppressWarnings("unused") + void setFillColor(int fillColor) { + mFillColor = fillColor; + } + + @SuppressWarnings("unused") + float getFillAlpha() { + return mFillAlpha; + } + + @SuppressWarnings("unused") + void setFillAlpha(float fillAlpha) { + mFillAlpha = fillAlpha; + } + + @SuppressWarnings("unused") + float getTrimPathStart() { + return mTrimPathStart; + } + + @SuppressWarnings("unused") + void setTrimPathStart(float trimPathStart) { + mTrimPathStart = trimPathStart; + } + + @SuppressWarnings("unused") + float getTrimPathEnd() { + return mTrimPathEnd; + } + + @SuppressWarnings("unused") + void setTrimPathEnd(float trimPathEnd) { + mTrimPathEnd = trimPathEnd; + } + + @SuppressWarnings("unused") + float getTrimPathOffset() { + return mTrimPathOffset; + } + + @SuppressWarnings("unused") + void setTrimPathOffset(float trimPathOffset) { + mTrimPathOffset = trimPathOffset; + } + } +} diff --git a/graphics/drawable/testanimated/Android.mk b/graphics/drawable/testanimated/Android.mk new file mode 100644 index 0000000000..c888d9e220 --- /dev/null +++ b/graphics/drawable/testanimated/Android.mk @@ -0,0 +1,35 @@ +# Copyright (C) 2015 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_MODULE_TAGS := tests + +LOCAL_SDK_VERSION := 11 + +LOCAL_SRC_FILES := $(call all-java-files-under, src) + +LOCAL_RESOURCE_DIR = \ + $(LOCAL_PATH)/res \ + frameworks/support/graphics/drawable/res \ + +LOCAL_PACKAGE_NAME := AndroidAnimatedVectorDrawableTests + +LOCAL_STATIC_JAVA_LIBRARIES := android-support-v11-animatedvectordrawable android-support-v4 + +LOCAL_AAPT_FLAGS += --auto-add-overlay --extra-packages android.support.graphics.drawable + +include $(BUILD_PACKAGE) diff --git a/graphics/drawable/testanimated/AndroidManifest.xml b/graphics/drawable/testanimated/AndroidManifest.xml new file mode 100644 index 0000000000..16171f27eb --- /dev/null +++ b/graphics/drawable/testanimated/AndroidManifest.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/graphics/drawable/testanimated/res/anim/alpha_animation_progress_bar.xml b/graphics/drawable/testanimated/res/anim/alpha_animation_progress_bar.xml new file mode 100644 index 0000000000..2463a8940d --- /dev/null +++ b/graphics/drawable/testanimated/res/anim/alpha_animation_progress_bar.xml @@ -0,0 +1,24 @@ + + + + + + + \ No newline at end of file diff --git a/graphics/drawable/testanimated/res/anim/animation_grouping_1_01.xml b/graphics/drawable/testanimated/res/anim/animation_grouping_1_01.xml new file mode 100644 index 0000000000..36c297fbe9 --- /dev/null +++ b/graphics/drawable/testanimated/res/anim/animation_grouping_1_01.xml @@ -0,0 +1,22 @@ + + + + diff --git a/graphics/drawable/testanimated/res/anim/trim_path_animation_progress_bar.xml b/graphics/drawable/testanimated/res/anim/trim_path_animation_progress_bar.xml new file mode 100644 index 0000000000..388c759a77 --- /dev/null +++ b/graphics/drawable/testanimated/res/anim/trim_path_animation_progress_bar.xml @@ -0,0 +1,45 @@ + + + + + + + + + + \ No newline at end of file diff --git a/graphics/drawable/testanimated/res/drawable/animation_vector_drawable_grouping_1.xml b/graphics/drawable/testanimated/res/drawable/animation_vector_drawable_grouping_1.xml new file mode 100644 index 0000000000..7c3b1de076 --- /dev/null +++ b/graphics/drawable/testanimated/res/drawable/animation_vector_drawable_grouping_1.xml @@ -0,0 +1,27 @@ + + + + + + + \ No newline at end of file diff --git a/graphics/drawable/testanimated/res/drawable/animation_vector_progress_bar.xml b/graphics/drawable/testanimated/res/drawable/animation_vector_progress_bar.xml new file mode 100644 index 0000000000..e37d2a1afe --- /dev/null +++ b/graphics/drawable/testanimated/res/drawable/animation_vector_progress_bar.xml @@ -0,0 +1,26 @@ + + + + + + \ No newline at end of file diff --git a/graphics/drawable/testanimated/res/drawable/app_sample_code.png b/graphics/drawable/testanimated/res/drawable/app_sample_code.png new file mode 100755 index 0000000000..66a198496c Binary files /dev/null and b/graphics/drawable/testanimated/res/drawable/app_sample_code.png differ diff --git a/graphics/drawable/testanimated/res/drawable/vector_drawable_grouping_1.xml b/graphics/drawable/testanimated/res/drawable/vector_drawable_grouping_1.xml new file mode 100644 index 0000000000..eceda711fa --- /dev/null +++ b/graphics/drawable/testanimated/res/drawable/vector_drawable_grouping_1.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/graphics/drawable/testanimated/res/drawable/vector_drawable_progress_bar.xml b/graphics/drawable/testanimated/res/drawable/vector_drawable_progress_bar.xml new file mode 100644 index 0000000000..0b8884b487 --- /dev/null +++ b/graphics/drawable/testanimated/res/drawable/vector_drawable_progress_bar.xml @@ -0,0 +1,50 @@ + + + + + + + + + + \ No newline at end of file diff --git a/graphics/drawable/testanimated/res/values/strings.xml b/graphics/drawable/testanimated/res/values/strings.xml new file mode 100644 index 0000000000..c5451c8845 --- /dev/null +++ b/graphics/drawable/testanimated/res/values/strings.xml @@ -0,0 +1,28 @@ + + + + + + "M 0,0 v 100 M 0,0 h 100" + "M300,70 l 0,-70 70,70 0,0 -70,70z" + "M300,70 l 0,-70 70,0 0,140 -70,0 z" + "M300,70 l 0,-70 70,0 0,70z M300,70 l 70,0 0,70 -70,0z" + "M300,35 l 0,-35 70,0 0,35z M300,105 l 70,0 0,35 -70,0z" + "m2.10001,-6c-1.9551,0 -0.5,0.02499 -2.10001,0.02499c-1.575,0 0.0031,-0.02499 -1.95,-0.02499c-2.543,0 -4,2.2816 -4,4.85001c0,3.52929 0.25,6.25 5.95,6.25c5.7,0 6,-2.72071 6,-6.25c0,-2.56841 -1.35699,-4.85001 -3.89999,-4.85001" + "m4.5,-7c-1.95509,0 -3.83009,1.26759 -4.5,3c-0.66991,-1.73241 -2.54691,-3 -4.5,-3c-2.543,0 -4.5,1.93159 -4.5,4.5c0,3.5293 3.793,6.2578 9,11.5c5.207,-5.2422 9,-7.9707 9,-11.5c0,-2.56841 -1.957,-4.5 -4.5,-4.5" + "M 0,0 l 200,0 l 0, 200 l -200, 0 z" + \ No newline at end of file diff --git a/graphics/drawable/testanimated/src/android/support/test/vectordrawable/TestAVDActivity.java b/graphics/drawable/testanimated/src/android/support/test/vectordrawable/TestAVDActivity.java new file mode 100644 index 0000000000..c63c69f947 --- /dev/null +++ b/graphics/drawable/testanimated/src/android/support/test/vectordrawable/TestAVDActivity.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2015 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.support.test.vectordrawable; + +import android.animation.ObjectAnimator; +import android.app.Activity; +import android.content.res.Resources; +import android.os.Bundle; +import android.support.graphics.drawable.AnimatedVectorDrawableCompat; +import android.util.Log; +import android.view.View; +import android.widget.Button; +import android.widget.LinearLayout; +import android.widget.ScrollView; +import android.widget.TextView; + +import java.text.DecimalFormat; + +public class TestAVDActivity extends Activity implements View.OnClickListener{ + private static final String LOG_TAG = "TestActivity"; + + private static final String LOGCAT = "VectorDrawable1"; + protected int[] icon = { + R.drawable.animation_vector_drawable_grouping_1, + R.drawable.animation_vector_progress_bar, + }; + + + @Override + protected void onCreate(Bundle savedInstanceState) { + ObjectAnimator oa = new ObjectAnimator(); + super.onCreate(savedInstanceState); + ScrollView scrollView = new ScrollView(this); + LinearLayout container = new LinearLayout(this); + scrollView.addView(container); + container.setOrientation(LinearLayout.VERTICAL); + Resources res = this.getResources(); + container.setBackgroundColor(0xFF888888); + AnimatedVectorDrawableCompat []d = new AnimatedVectorDrawableCompat[icon.length]; + long time = android.os.SystemClock.currentThreadTimeMillis(); + for (int i = 0; i < icon.length; i++) { + d[i] = AnimatedVectorDrawableCompat.create(this, icon[i]); + } + time = android.os.SystemClock.currentThreadTimeMillis()-time; + TextView t = new TextView(this); + DecimalFormat df = new DecimalFormat("#.##"); + t.setText("avgL=" + df.format(time / (icon.length)) + " ms"); + container.addView(t); + + addDrawableButtons(container, d); + + // Now test constant state and mutate a bit. + AnimatedVectorDrawableCompat []copies = new AnimatedVectorDrawableCompat[3]; + copies[0] = (AnimatedVectorDrawableCompat) d[0].getConstantState().newDrawable(); + copies[1] = (AnimatedVectorDrawableCompat) d[0].getConstantState().newDrawable(); + copies[2] = (AnimatedVectorDrawableCompat) d[0].getConstantState().newDrawable(); + copies[0].setAlpha(128); + + // Expect to see the copies[0, 1] are showing alpha 128, and [2] are showing 255. + copies[2].mutate(); + copies[2].setAlpha(255); + + addDrawableButtons(container, copies); + + setContentView(scrollView); + } + + private void addDrawableButtons(LinearLayout container, AnimatedVectorDrawableCompat[] d) { + for (int i = 0; i < d.length; i++) { + Button button = new Button(this); + button.setWidth(200); + button.setHeight(200); + button.setBackgroundDrawable(d[i]); + container.addView(button); + button.setOnClickListener(this); + } + } + + @Override + public void onClick(View v) { + AnimatedVectorDrawableCompat d = (AnimatedVectorDrawableCompat) v.getBackground(); + d.start(); + } +} diff --git a/graphics/drawable/teststatic/Android.mk b/graphics/drawable/teststatic/Android.mk new file mode 100644 index 0000000000..d8a0fd743f --- /dev/null +++ b/graphics/drawable/teststatic/Android.mk @@ -0,0 +1,38 @@ +# Copyright (C) 2015 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_MODULE_TAGS := tests + +LOCAL_SDK_VERSION := 7 + +LOCAL_SRC_FILES := $(call all-java-files-under, src) + +LOCAL_RESOURCE_DIR = \ + $(LOCAL_PATH)/res \ + frameworks/support/graphics/drawable/res \ + +LOCAL_PACKAGE_NAME := AndroidVectorDrawableTests + +LOCAL_STATIC_JAVA_LIBRARIES := android-support-v7-vectordrawable android-support-v4 + +LOCAL_AAPT_FLAGS := \ + --auto-add-overlay \ + --extra-packages android.support.graphics.drawable + +include $(BUILD_PACKAGE) + diff --git a/graphics/drawable/teststatic/AndroidManifest.xml b/graphics/drawable/teststatic/AndroidManifest.xml new file mode 100644 index 0000000000..19586fb5d0 --- /dev/null +++ b/graphics/drawable/teststatic/AndroidManifest.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/graphics/drawable/teststatic/res/drawable/app_sample_code.png b/graphics/drawable/teststatic/res/drawable/app_sample_code.png new file mode 100755 index 0000000000..66a198496c Binary files /dev/null and b/graphics/drawable/teststatic/res/drawable/app_sample_code.png differ diff --git a/graphics/drawable/teststatic/res/drawable/vector_drawable01.xml b/graphics/drawable/teststatic/res/drawable/vector_drawable01.xml new file mode 100644 index 0000000000..12357ef442 --- /dev/null +++ b/graphics/drawable/teststatic/res/drawable/vector_drawable01.xml @@ -0,0 +1,34 @@ + + + + + + + + + \ No newline at end of file diff --git a/graphics/drawable/teststatic/res/drawable/vector_drawable02.xml b/graphics/drawable/teststatic/res/drawable/vector_drawable02.xml new file mode 100644 index 0000000000..cb6b9dffbd --- /dev/null +++ b/graphics/drawable/teststatic/res/drawable/vector_drawable02.xml @@ -0,0 +1,37 @@ + + + + + + + + \ No newline at end of file diff --git a/graphics/drawable/teststatic/res/drawable/vector_drawable03.xml b/graphics/drawable/teststatic/res/drawable/vector_drawable03.xml new file mode 100644 index 0000000000..37d00863c4 --- /dev/null +++ b/graphics/drawable/teststatic/res/drawable/vector_drawable03.xml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/graphics/drawable/teststatic/res/drawable/vector_drawable04.xml b/graphics/drawable/teststatic/res/drawable/vector_drawable04.xml new file mode 100644 index 0000000000..4e2086f858 --- /dev/null +++ b/graphics/drawable/teststatic/res/drawable/vector_drawable04.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + diff --git a/graphics/drawable/teststatic/res/drawable/vector_drawable05.xml b/graphics/drawable/teststatic/res/drawable/vector_drawable05.xml new file mode 100644 index 0000000000..48801e3232 --- /dev/null +++ b/graphics/drawable/teststatic/res/drawable/vector_drawable05.xml @@ -0,0 +1,44 @@ + + + + + + + + \ No newline at end of file diff --git a/graphics/drawable/teststatic/res/drawable/vector_drawable06.xml b/graphics/drawable/teststatic/res/drawable/vector_drawable06.xml new file mode 100644 index 0000000000..24173e2fc3 --- /dev/null +++ b/graphics/drawable/teststatic/res/drawable/vector_drawable06.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + diff --git a/graphics/drawable/teststatic/res/drawable/vector_drawable07.xml b/graphics/drawable/teststatic/res/drawable/vector_drawable07.xml new file mode 100644 index 0000000000..90435d3bf9 --- /dev/null +++ b/graphics/drawable/teststatic/res/drawable/vector_drawable07.xml @@ -0,0 +1,30 @@ + + + + + + + diff --git a/graphics/drawable/teststatic/res/drawable/vector_drawable08.xml b/graphics/drawable/teststatic/res/drawable/vector_drawable08.xml new file mode 100644 index 0000000000..251d694d72 --- /dev/null +++ b/graphics/drawable/teststatic/res/drawable/vector_drawable08.xml @@ -0,0 +1,30 @@ + + + + + + + + diff --git a/graphics/drawable/teststatic/res/drawable/vector_drawable09.xml b/graphics/drawable/teststatic/res/drawable/vector_drawable09.xml new file mode 100644 index 0000000000..eccb0d092c --- /dev/null +++ b/graphics/drawable/teststatic/res/drawable/vector_drawable09.xml @@ -0,0 +1,33 @@ + + + + + + + + \ No newline at end of file diff --git a/graphics/drawable/teststatic/res/drawable/vector_drawable10.xml b/graphics/drawable/teststatic/res/drawable/vector_drawable10.xml new file mode 100644 index 0000000000..b26d30d087 --- /dev/null +++ b/graphics/drawable/teststatic/res/drawable/vector_drawable10.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/graphics/drawable/teststatic/res/drawable/vector_drawable11.xml b/graphics/drawable/teststatic/res/drawable/vector_drawable11.xml new file mode 100644 index 0000000000..eb440f5d31 --- /dev/null +++ b/graphics/drawable/teststatic/res/drawable/vector_drawable11.xml @@ -0,0 +1,36 @@ + + + + + + + + + \ No newline at end of file diff --git a/graphics/drawable/teststatic/res/drawable/vector_drawable12.xml b/graphics/drawable/teststatic/res/drawable/vector_drawable12.xml new file mode 100644 index 0000000000..94a23e8b23 --- /dev/null +++ b/graphics/drawable/teststatic/res/drawable/vector_drawable12.xml @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/graphics/drawable/teststatic/res/drawable/vector_drawable13.xml b/graphics/drawable/teststatic/res/drawable/vector_drawable13.xml new file mode 100644 index 0000000000..43fc7eadd2 --- /dev/null +++ b/graphics/drawable/teststatic/res/drawable/vector_drawable13.xml @@ -0,0 +1,38 @@ + + + + + + + + + \ No newline at end of file diff --git a/graphics/drawable/teststatic/res/drawable/vector_drawable14.xml b/graphics/drawable/teststatic/res/drawable/vector_drawable14.xml new file mode 100644 index 0000000000..5b4fdd1851 --- /dev/null +++ b/graphics/drawable/teststatic/res/drawable/vector_drawable14.xml @@ -0,0 +1,39 @@ + + + + + + + + \ No newline at end of file diff --git a/graphics/drawable/teststatic/res/drawable/vector_drawable15.xml b/graphics/drawable/teststatic/res/drawable/vector_drawable15.xml new file mode 100644 index 0000000000..f4ef87fc3f --- /dev/null +++ b/graphics/drawable/teststatic/res/drawable/vector_drawable15.xml @@ -0,0 +1,35 @@ + + + + + + + + \ No newline at end of file diff --git a/graphics/drawable/teststatic/res/drawable/vector_drawable16.xml b/graphics/drawable/teststatic/res/drawable/vector_drawable16.xml new file mode 100644 index 0000000000..0c64bca831 --- /dev/null +++ b/graphics/drawable/teststatic/res/drawable/vector_drawable16.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/graphics/drawable/teststatic/res/drawable/vector_drawable17.xml b/graphics/drawable/teststatic/res/drawable/vector_drawable17.xml new file mode 100644 index 0000000000..28cf09a1ee --- /dev/null +++ b/graphics/drawable/teststatic/res/drawable/vector_drawable17.xml @@ -0,0 +1,30 @@ + + + + + + + + diff --git a/graphics/drawable/teststatic/res/drawable/vector_drawable18.xml b/graphics/drawable/teststatic/res/drawable/vector_drawable18.xml new file mode 100644 index 0000000000..d66d4ff4c1 --- /dev/null +++ b/graphics/drawable/teststatic/res/drawable/vector_drawable18.xml @@ -0,0 +1,32 @@ + + + + + + + + \ No newline at end of file diff --git a/graphics/drawable/teststatic/res/drawable/vector_drawable19.xml b/graphics/drawable/teststatic/res/drawable/vector_drawable19.xml new file mode 100644 index 0000000000..3a6559d818 --- /dev/null +++ b/graphics/drawable/teststatic/res/drawable/vector_drawable19.xml @@ -0,0 +1,34 @@ + + + + + + + + \ No newline at end of file diff --git a/graphics/drawable/teststatic/res/drawable/vector_drawable20.xml b/graphics/drawable/teststatic/res/drawable/vector_drawable20.xml new file mode 100644 index 0000000000..d6fd7043eb --- /dev/null +++ b/graphics/drawable/teststatic/res/drawable/vector_drawable20.xml @@ -0,0 +1,35 @@ + + + + + + + + \ No newline at end of file diff --git a/graphics/drawable/teststatic/res/drawable/vector_drawable21.xml b/graphics/drawable/teststatic/res/drawable/vector_drawable21.xml new file mode 100644 index 0000000000..9136b73417 --- /dev/null +++ b/graphics/drawable/teststatic/res/drawable/vector_drawable21.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/graphics/drawable/teststatic/res/drawable/vector_drawable22.xml b/graphics/drawable/teststatic/res/drawable/vector_drawable22.xml new file mode 100644 index 0000000000..2b33a8903a --- /dev/null +++ b/graphics/drawable/teststatic/res/drawable/vector_drawable22.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/graphics/drawable/teststatic/res/drawable/vector_drawable23.xml b/graphics/drawable/teststatic/res/drawable/vector_drawable23.xml new file mode 100644 index 0000000000..d5759f99f9 --- /dev/null +++ b/graphics/drawable/teststatic/res/drawable/vector_drawable23.xml @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/graphics/drawable/teststatic/res/drawable/vector_drawable24.xml b/graphics/drawable/teststatic/res/drawable/vector_drawable24.xml new file mode 100644 index 0000000000..b05469281c --- /dev/null +++ b/graphics/drawable/teststatic/res/drawable/vector_drawable24.xml @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/graphics/drawable/teststatic/res/drawable/vector_drawable25.xml b/graphics/drawable/teststatic/res/drawable/vector_drawable25.xml new file mode 100644 index 0000000000..7a94ed6ca3 --- /dev/null +++ b/graphics/drawable/teststatic/res/drawable/vector_drawable25.xml @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/graphics/drawable/teststatic/res/drawable/vector_drawable26.xml b/graphics/drawable/teststatic/res/drawable/vector_drawable26.xml new file mode 100644 index 0000000000..b2dd4a3947 --- /dev/null +++ b/graphics/drawable/teststatic/res/drawable/vector_drawable26.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/graphics/drawable/teststatic/res/drawable/vector_drawable27.xml b/graphics/drawable/teststatic/res/drawable/vector_drawable27.xml new file mode 100644 index 0000000000..b8f88cee1c --- /dev/null +++ b/graphics/drawable/teststatic/res/drawable/vector_drawable27.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/graphics/drawable/teststatic/res/drawable/vector_drawable28.xml b/graphics/drawable/teststatic/res/drawable/vector_drawable28.xml new file mode 100644 index 0000000000..30c7fce346 --- /dev/null +++ b/graphics/drawable/teststatic/res/drawable/vector_drawable28.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/graphics/drawable/teststatic/res/drawable/vector_drawable29.xml b/graphics/drawable/teststatic/res/drawable/vector_drawable29.xml new file mode 100644 index 0000000000..2ac1d428f4 --- /dev/null +++ b/graphics/drawable/teststatic/res/drawable/vector_drawable29.xml @@ -0,0 +1,29 @@ + + + + + + + diff --git a/graphics/drawable/teststatic/res/drawable/vector_drawable30.xml b/graphics/drawable/teststatic/res/drawable/vector_drawable30.xml new file mode 100644 index 0000000000..6abb4552c8 --- /dev/null +++ b/graphics/drawable/teststatic/res/drawable/vector_drawable30.xml @@ -0,0 +1,29 @@ + + + + + + + diff --git a/graphics/drawable/teststatic/res/raw/vector_drawable01.xml b/graphics/drawable/teststatic/res/raw/vector_drawable01.xml new file mode 100644 index 0000000000..baa3fc7162 --- /dev/null +++ b/graphics/drawable/teststatic/res/raw/vector_drawable01.xml @@ -0,0 +1,32 @@ + + + + + + + diff --git a/graphics/drawable/teststatic/res/values/strings.xml b/graphics/drawable/teststatic/res/values/strings.xml new file mode 100644 index 0000000000..c5451c8845 --- /dev/null +++ b/graphics/drawable/teststatic/res/values/strings.xml @@ -0,0 +1,28 @@ + + + + + + "M 0,0 v 100 M 0,0 h 100" + "M300,70 l 0,-70 70,70 0,0 -70,70z" + "M300,70 l 0,-70 70,0 0,140 -70,0 z" + "M300,70 l 0,-70 70,0 0,70z M300,70 l 70,0 0,70 -70,0z" + "M300,35 l 0,-35 70,0 0,35z M300,105 l 70,0 0,35 -70,0z" + "m2.10001,-6c-1.9551,0 -0.5,0.02499 -2.10001,0.02499c-1.575,0 0.0031,-0.02499 -1.95,-0.02499c-2.543,0 -4,2.2816 -4,4.85001c0,3.52929 0.25,6.25 5.95,6.25c5.7,0 6,-2.72071 6,-6.25c0,-2.56841 -1.35699,-4.85001 -3.89999,-4.85001" + "m4.5,-7c-1.95509,0 -3.83009,1.26759 -4.5,3c-0.66991,-1.73241 -2.54691,-3 -4.5,-3c-2.543,0 -4.5,1.93159 -4.5,4.5c0,3.5293 3.793,6.2578 9,11.5c5.207,-5.2422 9,-7.9707 9,-11.5c0,-2.56841 -1.957,-4.5 -4.5,-4.5" + "M 0,0 l 200,0 l 0, 200 l -200, 0 z" + \ No newline at end of file diff --git a/graphics/drawable/teststatic/src/android/support/test/vectordrawable/TestActivity.java b/graphics/drawable/teststatic/src/android/support/test/vectordrawable/TestActivity.java new file mode 100644 index 0000000000..8bb766e511 --- /dev/null +++ b/graphics/drawable/teststatic/src/android/support/test/vectordrawable/TestActivity.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2015 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.support.test.vectordrawable; + +import android.app.Activity; +import android.content.res.Resources; +import android.graphics.PorterDuff.Mode; +import android.graphics.drawable.Drawable.ConstantState; +import android.os.Bundle; +import android.support.graphics.drawable.VectorDrawableCompat; +import android.widget.Button; +import android.widget.LinearLayout; +import android.widget.ScrollView; +import android.widget.TextView; + +import java.text.DecimalFormat; + +public class TestActivity extends Activity { + private static final String LOG_TAG = "TestActivity"; + + 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, + R.drawable.vector_drawable21, + R.drawable.vector_drawable22, + R.drawable.vector_drawable23, + R.drawable.vector_drawable24, + R.drawable.vector_drawable25, + R.drawable.vector_drawable26, + R.drawable.vector_drawable27, + R.drawable.vector_drawable28, + R.drawable.vector_drawable29, + R.drawable.vector_drawable30, + }; + + private static final int EXTRA_TESTS = 2; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + ScrollView scrollView = new ScrollView(this); + LinearLayout container = new LinearLayout(this); + scrollView.addView(container); + container.setOrientation(LinearLayout.VERTICAL); + Resources res = this.getResources(); + container.setBackgroundColor(0xFF888888); + VectorDrawableCompat []d = new VectorDrawableCompat[icon.length]; + long time = android.os.SystemClock.currentThreadTimeMillis(); + for (int i = 0; i < icon.length; i++) { + d[i] = VectorDrawableCompat.create(res, icon[i], getTheme()); + } + time = android.os.SystemClock.currentThreadTimeMillis()-time; + + // Testing Tint on one particular case. + d[3].setTint(0x8000FF00); + d[3].setTintMode(Mode.MULTIPLY); + + // Testing Constant State like operation by creating the first 2 icons + // from the 3rd one's constant state. + VectorDrawableCompat []extras = new VectorDrawableCompat[EXTRA_TESTS]; + ConstantState state = d[0].getConstantState(); + extras[0] = (VectorDrawableCompat) state.newDrawable(); + extras[1] = (VectorDrawableCompat) state.newDrawable(); + + // This alpha change is expected to affect both extra 0, 1, and d0. + extras[0].setAlpha(128); + + d[0].mutate(); + d[0].setAlpha(255); + + // Just show the average create time as the first view. + TextView t = new TextView(this); + DecimalFormat df = new DecimalFormat("#.##"); + t.setText("avgL=" + df.format(time / (icon.length)) + " ms"); + container.addView(t); + + addDrawableButtons(container, extras); + + addDrawableButtons(container, d); + + setContentView(scrollView); + } + + private void addDrawableButtons(LinearLayout container, VectorDrawableCompat[] d) { + // Add the VD into consequent views. + for (int i = 0; i < d.length; i++) { + Button button = new Button(this); + button.setWidth(200); + // Note that setBackgroundResource() will fail b/c createFromXmlInner() failed + // to recognize pre-L. + button.setBackgroundDrawable(d[i]); + container.addView(button); + } + } +} -- cgit v1.2.3