diff --git a/api/current.txt b/api/current.txt index bfbb17e94cead..6c7b5b8865029 100644 --- a/api/current.txt +++ b/api/current.txt @@ -506,6 +506,8 @@ package android { field public static final int encryptionAware = 16844038; // 0x1010506 field public static final int end = 16843996; // 0x10104dc field public static final int endColor = 16843166; // 0x101019e + field public static final int endX = 16844051; // 0x1010513 + field public static final int endY = 16844052; // 0x1010514 field public static final deprecated int endYear = 16843133; // 0x101017d field public static final int enterFadeDuration = 16843532; // 0x101030c field public static final int entries = 16842930; // 0x10100b2 @@ -881,6 +883,7 @@ package android { field public static final int numbersTextColor = 16843937; // 0x10104a1 field public static final deprecated int numeric = 16843109; // 0x1010165 field public static final int numericShortcut = 16843236; // 0x10101e4 + field public static final int offset = 16844053; // 0x1010515 field public static final int onClick = 16843375; // 0x101026f field public static final int oneshot = 16843159; // 0x1010197 field public static final int opacity = 16843550; // 0x101031e @@ -1128,6 +1131,8 @@ package android { field public static final int startColor = 16843165; // 0x101019d field public static final int startDelay = 16843746; // 0x10103e2 field public static final int startOffset = 16843198; // 0x10101be + field public static final int startX = 16844049; // 0x1010511 + field public static final int startY = 16844050; // 0x1010512 field public static final deprecated int startYear = 16843132; // 0x101017c field public static final int stateListAnimator = 16843848; // 0x1010448 field public static final int stateNotNeeded = 16842774; // 0x1010016 @@ -9949,7 +9954,7 @@ package android.content.res { method public final long skip(long) throws java.io.IOException; } - public class ColorStateList implements android.os.Parcelable { + public class ColorStateList extends android.content.res.ComplexColor implements android.os.Parcelable { ctor public ColorStateList(int[][], int[]); method public static deprecated android.content.res.ColorStateList createFromXml(android.content.res.Resources, org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException; method public static android.content.res.ColorStateList createFromXml(android.content.res.Resources, org.xmlpull.v1.XmlPullParser, android.content.res.Resources.Theme) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException; @@ -9958,13 +9963,18 @@ package android.content.res { method public int getColorForState(int[], int); method public int getDefaultColor(); method public boolean isOpaque(); - method public boolean isStateful(); method public static android.content.res.ColorStateList valueOf(int); method public android.content.res.ColorStateList withAlpha(int); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator CREATOR; } + public abstract class ComplexColor { + ctor public ComplexColor(); + method public abstract int getDefaultColor(); + method public boolean isStateful(); + } + public final class Configuration implements java.lang.Comparable android.os.Parcelable { ctor public Configuration(); ctor public Configuration(android.content.res.Configuration); @@ -10068,6 +10078,11 @@ package android.content.res { field public int uiMode; } + public class GradientColor extends android.content.res.ComplexColor { + method public static android.content.res.GradientColor createFromXml(android.content.res.Resources, android.content.res.XmlResourceParser, android.content.res.Resources.Theme) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException; + method public int getDefaultColor(); + } + public class ObbInfo implements android.os.Parcelable { method public int describeContents(); method public void writeToParcel(android.os.Parcel, int); @@ -10127,6 +10142,7 @@ package android.content.res { method public void getValue(java.lang.String, android.util.TypedValue, boolean) throws android.content.res.Resources.NotFoundException; method public void getValueForDensity(int, int, android.util.TypedValue, boolean) throws android.content.res.Resources.NotFoundException; method public android.content.res.XmlResourceParser getXml(int) throws android.content.res.Resources.NotFoundException; + method public android.content.res.ComplexColor loadComplexColor(android.util.TypedValue, int, android.content.res.Resources.Theme); method public final android.content.res.Resources.Theme newTheme(); method public android.content.res.TypedArray obtainAttributes(android.util.AttributeSet, int[]); method public android.content.res.TypedArray obtainTypedArray(int) throws android.content.res.Resources.NotFoundException; @@ -10162,6 +10178,7 @@ package android.content.res { method public int getChangingConfigurations(); method public int getColor(int, int); method public android.content.res.ColorStateList getColorStateList(int); + method public android.content.res.ComplexColor getComplexColor(int); method public float getDimension(int, float); method public int getDimensionPixelOffset(int, int); method public int getDimensionPixelSize(int, int); diff --git a/api/system-current.txt b/api/system-current.txt index c10954c2da4c6..16c189c9c43a0 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -601,6 +601,8 @@ package android { field public static final int encryptionAware = 16844038; // 0x1010506 field public static final int end = 16843996; // 0x10104dc field public static final int endColor = 16843166; // 0x101019e + field public static final int endX = 16844051; // 0x1010513 + field public static final int endY = 16844052; // 0x1010514 field public static final deprecated int endYear = 16843133; // 0x101017d field public static final int enterFadeDuration = 16843532; // 0x101030c field public static final int entries = 16842930; // 0x10100b2 @@ -976,6 +978,7 @@ package android { field public static final int numbersTextColor = 16843937; // 0x10104a1 field public static final deprecated int numeric = 16843109; // 0x1010165 field public static final int numericShortcut = 16843236; // 0x10101e4 + field public static final int offset = 16844053; // 0x1010515 field public static final int onClick = 16843375; // 0x101026f field public static final int oneshot = 16843159; // 0x1010197 field public static final int opacity = 16843550; // 0x101031e @@ -1227,6 +1230,8 @@ package android { field public static final int startColor = 16843165; // 0x101019d field public static final int startDelay = 16843746; // 0x10103e2 field public static final int startOffset = 16843198; // 0x10101be + field public static final int startX = 16844049; // 0x1010511 + field public static final int startY = 16844050; // 0x1010512 field public static final deprecated int startYear = 16843132; // 0x101017c field public static final int stateListAnimator = 16843848; // 0x1010448 field public static final int stateNotNeeded = 16842774; // 0x1010016 @@ -10349,7 +10354,7 @@ package android.content.res { method public final long skip(long) throws java.io.IOException; } - public class ColorStateList implements android.os.Parcelable { + public class ColorStateList extends android.content.res.ComplexColor implements android.os.Parcelable { ctor public ColorStateList(int[][], int[]); method public static deprecated android.content.res.ColorStateList createFromXml(android.content.res.Resources, org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException; method public static android.content.res.ColorStateList createFromXml(android.content.res.Resources, org.xmlpull.v1.XmlPullParser, android.content.res.Resources.Theme) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException; @@ -10358,13 +10363,18 @@ package android.content.res { method public int getColorForState(int[], int); method public int getDefaultColor(); method public boolean isOpaque(); - method public boolean isStateful(); method public static android.content.res.ColorStateList valueOf(int); method public android.content.res.ColorStateList withAlpha(int); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator CREATOR; } + public abstract class ComplexColor { + ctor public ComplexColor(); + method public abstract int getDefaultColor(); + method public boolean isStateful(); + } + public final class Configuration implements java.lang.Comparable android.os.Parcelable { ctor public Configuration(); ctor public Configuration(android.content.res.Configuration); @@ -10468,6 +10478,11 @@ package android.content.res { field public int uiMode; } + public class GradientColor extends android.content.res.ComplexColor { + method public static android.content.res.GradientColor createFromXml(android.content.res.Resources, android.content.res.XmlResourceParser, android.content.res.Resources.Theme) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException; + method public int getDefaultColor(); + } + public class ObbInfo implements android.os.Parcelable { method public int describeContents(); method public void writeToParcel(android.os.Parcel, int); @@ -10527,6 +10542,7 @@ package android.content.res { method public void getValue(java.lang.String, android.util.TypedValue, boolean) throws android.content.res.Resources.NotFoundException; method public void getValueForDensity(int, int, android.util.TypedValue, boolean) throws android.content.res.Resources.NotFoundException; method public android.content.res.XmlResourceParser getXml(int) throws android.content.res.Resources.NotFoundException; + method public android.content.res.ComplexColor loadComplexColor(android.util.TypedValue, int, android.content.res.Resources.Theme); method public final android.content.res.Resources.Theme newTheme(); method public android.content.res.TypedArray obtainAttributes(android.util.AttributeSet, int[]); method public android.content.res.TypedArray obtainTypedArray(int) throws android.content.res.Resources.NotFoundException; @@ -10562,6 +10578,7 @@ package android.content.res { method public int getChangingConfigurations(); method public int getColor(int, int); method public android.content.res.ColorStateList getColorStateList(int); + method public android.content.res.ComplexColor getComplexColor(int); method public float getDimension(int, float); method public int getDimensionPixelOffset(int, int); method public int getDimensionPixelSize(int, int); diff --git a/api/test-current.txt b/api/test-current.txt index c076ad6e20e7c..8fc63fe2c7838 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -506,6 +506,8 @@ package android { field public static final int encryptionAware = 16844038; // 0x1010506 field public static final int end = 16843996; // 0x10104dc field public static final int endColor = 16843166; // 0x101019e + field public static final int endX = 16844051; // 0x1010513 + field public static final int endY = 16844052; // 0x1010514 field public static final deprecated int endYear = 16843133; // 0x101017d field public static final int enterFadeDuration = 16843532; // 0x101030c field public static final int entries = 16842930; // 0x10100b2 @@ -881,6 +883,7 @@ package android { field public static final int numbersTextColor = 16843937; // 0x10104a1 field public static final deprecated int numeric = 16843109; // 0x1010165 field public static final int numericShortcut = 16843236; // 0x10101e4 + field public static final int offset = 16844053; // 0x1010515 field public static final int onClick = 16843375; // 0x101026f field public static final int oneshot = 16843159; // 0x1010197 field public static final int opacity = 16843550; // 0x101031e @@ -1128,6 +1131,8 @@ package android { field public static final int startColor = 16843165; // 0x101019d field public static final int startDelay = 16843746; // 0x10103e2 field public static final int startOffset = 16843198; // 0x10101be + field public static final int startX = 16844049; // 0x1010511 + field public static final int startY = 16844050; // 0x1010512 field public static final deprecated int startYear = 16843132; // 0x101017c field public static final int stateListAnimator = 16843848; // 0x1010448 field public static final int stateNotNeeded = 16842774; // 0x1010016 @@ -9957,7 +9962,7 @@ package android.content.res { method public final long skip(long) throws java.io.IOException; } - public class ColorStateList implements android.os.Parcelable { + public class ColorStateList extends android.content.res.ComplexColor implements android.os.Parcelable { ctor public ColorStateList(int[][], int[]); method public static deprecated android.content.res.ColorStateList createFromXml(android.content.res.Resources, org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException; method public static android.content.res.ColorStateList createFromXml(android.content.res.Resources, org.xmlpull.v1.XmlPullParser, android.content.res.Resources.Theme) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException; @@ -9966,13 +9971,18 @@ package android.content.res { method public int getColorForState(int[], int); method public int getDefaultColor(); method public boolean isOpaque(); - method public boolean isStateful(); method public static android.content.res.ColorStateList valueOf(int); method public android.content.res.ColorStateList withAlpha(int); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator CREATOR; } + public abstract class ComplexColor { + ctor public ComplexColor(); + method public abstract int getDefaultColor(); + method public boolean isStateful(); + } + public final class Configuration implements java.lang.Comparable android.os.Parcelable { ctor public Configuration(); ctor public Configuration(android.content.res.Configuration); @@ -10076,6 +10086,11 @@ package android.content.res { field public int uiMode; } + public class GradientColor extends android.content.res.ComplexColor { + method public static android.content.res.GradientColor createFromXml(android.content.res.Resources, android.content.res.XmlResourceParser, android.content.res.Resources.Theme) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException; + method public int getDefaultColor(); + } + public class ObbInfo implements android.os.Parcelable { method public int describeContents(); method public void writeToParcel(android.os.Parcel, int); @@ -10135,6 +10150,7 @@ package android.content.res { method public void getValue(java.lang.String, android.util.TypedValue, boolean) throws android.content.res.Resources.NotFoundException; method public void getValueForDensity(int, int, android.util.TypedValue, boolean) throws android.content.res.Resources.NotFoundException; method public android.content.res.XmlResourceParser getXml(int) throws android.content.res.Resources.NotFoundException; + method public android.content.res.ComplexColor loadComplexColor(android.util.TypedValue, int, android.content.res.Resources.Theme); method public final android.content.res.Resources.Theme newTheme(); method public android.content.res.TypedArray obtainAttributes(android.util.AttributeSet, int[]); method public android.content.res.TypedArray obtainTypedArray(int) throws android.content.res.Resources.NotFoundException; @@ -10170,6 +10186,7 @@ package android.content.res { method public int getChangingConfigurations(); method public int getColor(int, int); method public android.content.res.ColorStateList getColorStateList(int); + method public android.content.res.ComplexColor getComplexColor(int); method public float getDimension(int, float); method public int getDimensionPixelOffset(int, int); method public int getDimensionPixelSize(int, int); diff --git a/core/java/android/content/res/ColorStateList.java b/core/java/android/content/res/ColorStateList.java index 19921b548aacc..9e1b312c8372c 100644 --- a/core/java/android/content/res/ColorStateList.java +++ b/core/java/android/content/res/ColorStateList.java @@ -69,7 +69,7 @@ import java.util.Arrays; * href="{@docRoot}guide/topics/resources/color-list-resource.html">Color State * List Resource.

*/ -public class ColorStateList implements Parcelable { +public class ColorStateList extends ComplexColor implements Parcelable { private static final String TAG = "ColorStateList"; private static final int DEFAULT_COLOR = Color.RED; @@ -209,7 +209,7 @@ public class ColorStateList implements Parcelable { * @return A new color state list for the current tag. */ @NonNull - private static ColorStateList createFromXmlInner(@NonNull Resources r, + static ColorStateList createFromXmlInner(@NonNull Resources r, @NonNull XmlPullParser parser, @NonNull AttributeSet attrs, @Nullable Theme theme) throws XmlPullParserException, IOException { final String name = parser.getName(); @@ -340,6 +340,7 @@ public class ColorStateList implements Parcelable { * @return whether a theme can be applied to this color state list * @hide only for resource preloading */ + @Override public boolean canApplyTheme() { return mThemeAttrs != null; } @@ -419,6 +420,7 @@ public class ColorStateList implements Parcelable { * attributes * @hide only for resource preloading */ + @Override public ColorStateList obtainForTheme(Theme t) { if (t == null || !canApplyTheme()) { return this; @@ -460,6 +462,7 @@ public class ColorStateList implements Parcelable { * otherwise. * @see #getColorForState(int[], int) */ + @Override public boolean isStateful() { return mStateSpecs.length > 1; } @@ -602,14 +605,14 @@ public class ColorStateList implements Parcelable { * @return a factory that can create new instances of this ColorStateList * @hide only for resource preloading */ - public ConstantState getConstantState() { + public ConstantState getConstantState() { if (mFactory == null) { mFactory = new ColorStateListFactory(this); } return mFactory; } - private static class ColorStateListFactory extends ConstantState { + private static class ColorStateListFactory extends ConstantState { private final ColorStateList mSrc; public ColorStateListFactory(ColorStateList src) { @@ -628,7 +631,7 @@ public class ColorStateList implements Parcelable { @Override public ColorStateList newInstance(Resources res, Theme theme) { - return mSrc.obtainForTheme(theme); + return (ColorStateList) mSrc.obtainForTheme(theme); } } diff --git a/core/java/android/content/res/ComplexColor.java b/core/java/android/content/res/ComplexColor.java new file mode 100644 index 0000000000000..d96ec62749f08 --- /dev/null +++ b/core/java/android/content/res/ComplexColor.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2016 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.content.res; + +import android.annotation.ColorInt; +import android.content.res.Resources.Theme; +import android.graphics.Color; + +/** + * Defines an abstract class for the complex color information, like + * {@link android.content.res.ColorStateList} or {@link android.content.res.GradientColor} + */ +public abstract class ComplexColor { + /** + * @return {@code true} if this ComplexColor changes color based on state, {@code false} + * otherwise. + */ + public boolean isStateful() { return false; } + + /** + * @return the default color. + */ + @ColorInt + public abstract int getDefaultColor(); + + /** + * @hide only for resource preloading + * + */ + public abstract ConstantState getConstantState(); + + /** + * @hide only for resource preloading + */ + public abstract boolean canApplyTheme(); + + /** + * @hide only for resource preloading + */ + public abstract ComplexColor obtainForTheme(Theme t); +} diff --git a/core/java/android/content/res/GradientColor.java b/core/java/android/content/res/GradientColor.java new file mode 100644 index 0000000000000..98ef2eaa5e6d3 --- /dev/null +++ b/core/java/android/content/res/GradientColor.java @@ -0,0 +1,523 @@ +/* + * Copyright (C) 2016 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.content.res; + +import android.annotation.ColorInt; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.res.Resources.Theme; + +import com.android.internal.R; +import com.android.internal.util.GrowingArrayUtils; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import android.graphics.LinearGradient; +import android.graphics.RadialGradient; +import android.graphics.Shader; +import android.graphics.SweepGradient; +import android.graphics.drawable.GradientDrawable; +import android.util.AttributeSet; +import android.util.Log; +import android.util.Xml; + +import java.io.IOException; + + +public class GradientColor extends ComplexColor { + private static final String TAG = "GradientColor"; + + private static final boolean DBG_GRADIENT = false; + + /** Lazily-created factory for this GradientColor. */ + private GradientColorFactory mFactory; + + private int mChangingConfigurations; + private int mDefaultColor; + + // After parsing all the attributes from XML, this shader is the ultimate result containing + // all the XML information. + private Shader mShader = null; + + // Below are the attributes at the root element + private int mGradientType = GradientDrawable.LINEAR_GRADIENT; + + private float mCenterX = 0f; + private float mCenterY = 0f; + + private float mStartX = 0f; + private float mStartY = 0f; + private float mEndX = 0f; + private float mEndY = 0f; + + private int mStartColor = 0; + private int mCenterColor = 0; + private int mEndColor = 0; + private boolean mHasCenterColor = false; + + private float mGradientRadius = 0f; + + // Below are the attributes for the element. + private int[] mItemColors; + private float[] mItemOffsets; + + // Theme attributes for the root and item elements. + private int[] mThemeAttrs; + private int[][] mItemsThemeAttrs; + + private GradientColor() { + } + + private GradientColor(GradientColor copy) { + if (copy != null) { + mChangingConfigurations = copy.mChangingConfigurations; + mDefaultColor = copy.mDefaultColor; + mShader = copy.mShader; + mGradientType = copy.mGradientType; + mCenterX = copy.mCenterX; + mCenterY = copy.mCenterY; + mStartX = copy.mStartX; + mStartY = copy.mStartY; + mEndX = copy.mEndX; + mEndY = copy.mEndY; + mStartColor = copy.mStartColor; + mCenterColor = copy.mCenterColor; + mEndColor = copy.mEndColor; + mHasCenterColor = copy.mHasCenterColor; + mGradientRadius = copy.mGradientRadius; + + if (copy.mItemColors != null) { + mItemColors = copy.mItemColors.clone(); + } + if (copy.mItemOffsets != null) { + mItemOffsets = copy.mItemOffsets.clone(); + } + + if (copy.mThemeAttrs != null) { + mThemeAttrs = copy.mThemeAttrs.clone(); + } + if (copy.mItemsThemeAttrs != null) { + mItemsThemeAttrs = copy.mItemsThemeAttrs.clone(); + } + } + } + + /** + * Update the root level's attributes, either for inflate or applyTheme. + */ + private void updateRootElementState(TypedArray a) { + // Extract the theme attributes, if any. + mThemeAttrs = a.extractThemeAttrs(); + + mStartX = a.getFloat( + R.styleable.GradientColor_startX, mStartX); + mStartY = a.getFloat( + R.styleable.GradientColor_startY, mStartY); + mEndX = a.getFloat( + R.styleable.GradientColor_endX, mEndX); + mEndY = a.getFloat( + R.styleable.GradientColor_endY, mEndY); + + mCenterX = a.getFloat( + R.styleable.GradientColor_centerX, mCenterX); + mCenterY = a.getFloat( + R.styleable.GradientColor_centerY, mCenterY); + + mGradientType = a.getInt( + R.styleable.GradientColor_type, mGradientType); + + mStartColor = a.getColor( + R.styleable.GradientColor_startColor, mStartColor); + mHasCenterColor |= a.hasValue( + R.styleable.GradientColor_centerColor); + mCenterColor = a.getColor( + R.styleable.GradientColor_centerColor, mCenterColor); + mEndColor = a.getColor( + R.styleable.GradientColor_endColor, mEndColor); + + if (DBG_GRADIENT) { + Log.v(TAG, "hasCenterColor is " + mHasCenterColor); + if (mHasCenterColor) { + Log.v(TAG, "centerColor:" + mCenterColor); + } + Log.v(TAG, "startColor: " + mStartColor); + Log.v(TAG, "endColor: " + mEndColor); + } + + mGradientRadius = a.getFloat(R.styleable.GradientColor_gradientRadius, + mGradientRadius); + } + + /** + * Check if the XML content is valid. + * + * @throws XmlPullParserException if errors were found. + */ + private void validateXmlContent() throws XmlPullParserException { + if (mGradientRadius <= 0 + && mGradientType == GradientDrawable.RADIAL_GRADIENT) { + throw new XmlPullParserException( + " tag requires 'gradientRadius' " + + "attribute with radial type"); + } + } + + /** + * The shader information will be applied to the native VectorDrawable's path. + * @hide + */ + public Shader getShader() { + return mShader; + } + + /** + * A public method to create GradientColor from a XML resource. + */ + public static GradientColor createFromXml(Resources r, XmlResourceParser parser, Theme theme) + throws XmlPullParserException, IOException { + final AttributeSet attrs = Xml.asAttributeSet(parser); + + int type; + while ((type = parser.next()) != XmlPullParser.START_TAG + && type != XmlPullParser.END_DOCUMENT) { + // Seek parser to start tag. + } + + if (type != XmlPullParser.START_TAG) { + throw new XmlPullParserException("No start tag found"); + } + + return createFromXmlInner(r, parser, attrs, theme); + } + + /** + * Create from inside an XML document. Called on a parser positioned at a + * tag in an XML document, tries to create a GradientColor from that tag. + * + * @return A new GradientColor for the current tag. + * @throws XmlPullParserException if the current tag is not <gradient> + */ + @NonNull + static GradientColor createFromXmlInner(@NonNull Resources r, + @NonNull XmlPullParser parser, @NonNull AttributeSet attrs, @Nullable Theme theme) + throws XmlPullParserException, IOException { + final String name = parser.getName(); + if (!name.equals("gradient")) { + throw new XmlPullParserException( + parser.getPositionDescription() + ": invalid gradient color tag " + name); + } + + final GradientColor gradientColor = new GradientColor(); + gradientColor.inflate(r, parser, attrs, theme); + return gradientColor; + } + + /** + * Fill in this object based on the contents of an XML "gradient" element. + */ + private void inflate(@NonNull Resources r, @NonNull XmlPullParser parser, + @NonNull AttributeSet attrs, @Nullable Theme theme) + throws XmlPullParserException, IOException { + final TypedArray a = Resources.obtainAttributes(r, theme, attrs, R.styleable.GradientColor); + updateRootElementState(a); + mChangingConfigurations |= a.getChangingConfigurations(); + a.recycle(); + + // Check correctness and throw exception if errors found. + validateXmlContent(); + + inflateChildElements(r, parser, attrs, theme); + + onColorsChange(); + } + + /** + * Inflates child elements "item"s for each color stop. + * + * Note that at root level, we need to save ThemeAttrs for theme applied later. + * Here similarly, at each child item, we need to save the theme's attributes, and apply theme + * later as applyItemsAttrsTheme(). + */ + private void inflateChildElements(@NonNull Resources r, @NonNull XmlPullParser parser, + @NonNull AttributeSet attrs, @NonNull Theme theme) + throws XmlPullParserException, IOException { + final int innerDepth = parser.getDepth() + 1; + int type; + int depth; + + // Pre-allocate the array with some size, for better performance. + float[] offsetList = new float[20]; + int[] colorList = new int[offsetList.length]; + int[][] themeAttrsList = new int[offsetList.length][]; + + int listSize = 0; + boolean hasUnresolvedAttrs = false; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && ((depth = parser.getDepth()) >= innerDepth + || type != XmlPullParser.END_TAG)) { + if (type != XmlPullParser.START_TAG) { + continue; + } + if (depth > innerDepth || !parser.getName().equals("item")) { + continue; + } + + final TypedArray a = Resources.obtainAttributes(r, theme, attrs, + R.styleable.GradientColorItem); + boolean hasColor = a.hasValue(R.styleable.GradientColorItem_color); + boolean hasOffset = a.hasValue(R.styleable.GradientColorItem_offset); + if (!hasColor || !hasOffset) { + throw new XmlPullParserException( + parser.getPositionDescription() + + ": tag requires a 'color' attribute and a 'offset' " + + "attribute!"); + } + + final int[] themeAttrs = a.extractThemeAttrs(); + int color = a.getColor(R.styleable.GradientColorItem_color, 0); + float offset = a.getFloat(R.styleable.GradientColorItem_offset, 0); + + if (DBG_GRADIENT) { + Log.v(TAG, "new item color " + color + " " + Integer.toHexString(color)); + Log.v(TAG, "offset" + offset); + } + mChangingConfigurations |= a.getChangingConfigurations(); + a.recycle(); + + if (themeAttrs != null) { + hasUnresolvedAttrs = true; + } + + colorList = GrowingArrayUtils.append(colorList, listSize, color); + offsetList = GrowingArrayUtils.append(offsetList, listSize, offset); + themeAttrsList = GrowingArrayUtils.append(themeAttrsList, listSize, themeAttrs); + listSize++; + } + if (listSize > 0) { + if (hasUnresolvedAttrs) { + mItemsThemeAttrs = new int[listSize][]; + System.arraycopy(themeAttrsList, 0, mItemsThemeAttrs, 0, listSize); + } else { + mItemsThemeAttrs = null; + } + + mItemColors = new int[listSize]; + mItemOffsets = new float[listSize]; + System.arraycopy(colorList, 0, mItemColors, 0, listSize); + System.arraycopy(offsetList, 0, mItemOffsets, 0, listSize); + } + } + + /** + * Apply theme to all the items. + */ + private void applyItemsAttrsTheme(Theme t) { + if (mItemsThemeAttrs == null) { + return; + } + + boolean hasUnresolvedAttrs = false; + + final int[][] themeAttrsList = mItemsThemeAttrs; + final int N = themeAttrsList.length; + for (int i = 0; i < N; i++) { + if (themeAttrsList[i] != null) { + final TypedArray a = t.resolveAttributes(themeAttrsList[i], + R.styleable.GradientColorItem); + + // Extract the theme attributes, if any, before attempting to + // read from the typed array. This prevents a crash if we have + // unresolved attrs. + themeAttrsList[i] = a.extractThemeAttrs(themeAttrsList[i]); + if (themeAttrsList[i] != null) { + hasUnresolvedAttrs = true; + } + + mItemColors[i] = a.getColor(R.styleable.GradientColorItem_color, mItemColors[i]); + mItemOffsets[i] = a.getFloat(R.styleable.GradientColorItem_offset, mItemOffsets[i]); + if (DBG_GRADIENT) { + Log.v(TAG, "applyItemsAttrsTheme Colors[i] " + i + " " + + Integer.toHexString(mItemColors[i])); + Log.v(TAG, "Offsets[i] " + i + " " + mItemOffsets[i]); + } + + // Account for any configuration changes. + mChangingConfigurations |= a.getChangingConfigurations(); + + a.recycle(); + } + } + + if (!hasUnresolvedAttrs) { + mItemsThemeAttrs = null; + } + } + + private void onColorsChange() { + int[] tempColors = null; + float[] tempOffsets = null; + + if (mItemColors != null) { + int length = mItemColors.length; + tempColors = new int[length]; + tempOffsets = new float[length]; + + for (int i = 0; i < length; i++) { + tempColors[i] = mItemColors[i]; + tempOffsets[i] = mItemOffsets[i]; + } + } else { + if (mHasCenterColor) { + tempColors = new int[3]; + tempColors[0] = mStartColor; + tempColors[1] = mCenterColor; + tempColors[2] = mEndColor; + + tempOffsets = new float[3]; + tempOffsets[0] = 0.0f; + // Since 0.5f is default value, try to take the one that isn't 0.5f + tempOffsets[1] = 0.5f; + tempOffsets[2] = 1f; + } else { + tempColors = new int[2]; + tempColors[0] = mStartColor; + tempColors[1] = mEndColor; + } + } + if (tempColors.length < 2) { + Log.w(TAG, " tag requires 2 color values specified!" + tempColors.length + + " " + tempColors); + } + + if (mGradientType == GradientDrawable.LINEAR_GRADIENT) { + mShader = new LinearGradient(mStartX, mStartY, mEndX, mEndY, tempColors, tempOffsets, + Shader.TileMode.CLAMP); + } else { + if (mGradientType == GradientDrawable.RADIAL_GRADIENT) { + mShader = new RadialGradient(mCenterX, mCenterY, mGradientRadius, tempColors, + tempOffsets, Shader.TileMode.CLAMP); + } else { + mShader = new SweepGradient(mCenterX, mCenterY, tempColors, tempOffsets); + } + } + mDefaultColor = tempColors[0]; + } + + /** + * For Gradient color, the default color is not very useful, since the gradient will override + * the color information anyway. + */ + @Override + @ColorInt + public int getDefaultColor() { + return mDefaultColor; + } + + /** + * Similar to ColorStateList, setup constant state and its factory. + * @hide only for resource preloading + */ + @Override + public ConstantState getConstantState() { + if (mFactory == null) { + mFactory = new GradientColorFactory(this); + } + return mFactory; + } + + private static class GradientColorFactory extends ConstantState { + private final GradientColor mSrc; + + public GradientColorFactory(GradientColor src) { + mSrc = src; + } + + @Override + public int getChangingConfigurations() { + return mSrc.mChangingConfigurations; + } + + @Override + public GradientColor newInstance() { + return mSrc; + } + + @Override + public GradientColor newInstance(Resources res, Theme theme) { + return mSrc.obtainForTheme(theme); + } + } + + /** + * Returns an appropriately themed gradient color. + * + * @param t the theme to apply + * @return a copy of the gradient color the theme applied, or the + * gradient itself if there were no unresolved theme + * attributes + * @hide only for resource preloading + */ + @Override + public GradientColor obtainForTheme(Theme t) { + if (t == null || !canApplyTheme()) { + return this; + } + + final GradientColor clone = new GradientColor(this); + clone.applyTheme(t); + return clone; + } + + private void applyTheme(Theme t) { + if (mThemeAttrs != null) { + applyRootAttrsTheme(t); + } + if (mItemsThemeAttrs != null) { + applyItemsAttrsTheme(t); + } + onColorsChange(); + } + + private void applyRootAttrsTheme(Theme t) { + final TypedArray a = t.resolveAttributes(mThemeAttrs, R.styleable.GradientColor); + // mThemeAttrs will be set to null if if there are no theme attributes in the + // typed array. + mThemeAttrs = a.extractThemeAttrs(mThemeAttrs); + // merging the attributes update inside the updateRootElementState(). + updateRootElementState(a); + + // Account for any configuration changes. + mChangingConfigurations |= a.getChangingConfigurations(); + a.recycle(); + } + + + /** + * Returns whether a theme can be applied to this gradient color, which + * usually indicates that the gradient color has unresolved theme + * attributes. + * + * @return whether a theme can be applied to this gradient color. + * @hide only for resource preloading + */ + @Override + public boolean canApplyTheme() { + return mThemeAttrs != null || mItemsThemeAttrs != null; + } + +} diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java index e4044296dac4f..4967d05e27270 100644 --- a/core/java/android/content/res/Resources.java +++ b/core/java/android/content/res/Resources.java @@ -56,6 +56,7 @@ import android.util.LongSparseArray; import android.util.Pools.SynchronizedPool; import android.util.Slog; import android.util.TypedValue; +import android.util.Xml; import android.view.ViewDebug; import android.view.ViewHierarchyEncoder; @@ -115,8 +116,8 @@ public class Resources { private static final LongSparseArray[] sPreloadedDrawables; private static final LongSparseArray sPreloadedColorDrawables = new LongSparseArray<>(); - private static final LongSparseArray> - sPreloadedColorStateLists = new LongSparseArray<>(); + private static final LongSparseArray> + sPreloadedComplexColors = new LongSparseArray<>(); // Pool of TypedArrays targeted to this Resources object. final SynchronizedPool mTypedArrayPool = new SynchronizedPool<>(5); @@ -133,7 +134,7 @@ public class Resources { private final Configuration mTmpConfig = new Configuration(); private final DrawableCache mDrawableCache = new DrawableCache(this); private final DrawableCache mColorDrawableCache = new DrawableCache(this); - private final ConfigurationBoundResourceCache mColorStateListCache = + private final ConfigurationBoundResourceCache mComplexColorCache = new ConfigurationBoundResourceCache<>(this); private final ConfigurationBoundResourceCache mAnimatorCache = new ConfigurationBoundResourceCache<>(this); @@ -1987,7 +1988,7 @@ public class Resources { mDrawableCache.onConfigurationChange(configChanges); mColorDrawableCache.onConfigurationChange(configChanges); - mColorStateListCache.onConfigurationChange(configChanges); + mComplexColorCache.onConfigurationChange(configChanges); mAnimatorCache.onConfigurationChange(configChanges); mStateListAnimatorCache.onConfigurationChange(configChanges); @@ -2613,6 +2614,82 @@ public class Resources { return dr; } + /** + * Given the value and id, we can get the XML filename as in value.data, based on that, we + * first try to load CSL from the cache. If not found, try to get from the constant state. + * Last, parse the XML and generate the CSL. + */ + private ComplexColor loadComplexColorFromName(Theme theme, TypedValue value, int id) { + final long key = (((long) value.assetCookie) << 32) | value.data; + final ConfigurationBoundResourceCache cache = mComplexColorCache; + ComplexColor complexColor = cache.getInstance(key, theme); + if (complexColor != null) { + return complexColor; + } + + final android.content.res.ConstantState factory = + sPreloadedComplexColors.get(key); + + if (factory != null) { + complexColor = factory.newInstance(this, theme); + } + if (complexColor == null) { + complexColor = loadComplexColorForCookie(value, id, theme); + } + + if (complexColor != null) { + if (mPreloading) { + if (verifyPreloadConfig(value.changingConfigurations, 0, value.resourceId, + "color")) { + sPreloadedComplexColors.put(key, complexColor.getConstantState()); + } + } else { + cache.put(key, theme, complexColor.getConstantState()); + } + } + return complexColor; + } + + @Nullable + public ComplexColor loadComplexColor(@NonNull TypedValue value, int id, Theme theme) { + if (TRACE_FOR_PRELOAD) { + // Log only framework resources + if ((id >>> 24) == 0x1) { + final String name = getResourceName(id); + if (name != null) android.util.Log.d("loadComplexColor", name); + } + } + + final long key = (((long) value.assetCookie) << 32) | value.data; + + // Handle inline color definitions. + if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT + && value.type <= TypedValue.TYPE_LAST_COLOR_INT) { + return getColorStateListFromInt(value, key); + } + + final String file = value.string.toString(); + + ComplexColor complexColor; + if (file.endsWith(".xml")) { + try { + complexColor = loadComplexColorFromName(theme, value, id); + } catch (Exception e) { + final NotFoundException rnf = new NotFoundException( + "File " + file + " from complex color resource ID #0x" + + Integer.toHexString(id)); + rnf.initCause(e); + throw rnf; + } + } else { + throw new NotFoundException( + "File " + file + " from drawable resource ID #0x" + + Integer.toHexString(id) + ": .xml extension required"); + } + + return complexColor; + } + @Nullable ColorStateList loadColorStateList(TypedValue value, int id, Theme theme) throws NotFoundException { @@ -2626,63 +2703,57 @@ public class Resources { final long key = (((long) value.assetCookie) << 32) | value.data; - ColorStateList csl; - // Handle inline color definitions. if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT && value.type <= TypedValue.TYPE_LAST_COLOR_INT) { - final android.content.res.ConstantState factory = - sPreloadedColorStateLists.get(key); - if (factory != null) { - return factory.newInstance(); - } - - csl = ColorStateList.valueOf(value.data); - - if (mPreloading) { - if (verifyPreloadConfig(value.changingConfigurations, 0, value.resourceId, - "color")) { - sPreloadedColorStateLists.put(key, csl.getConstantState()); - } - } - - return csl; + return getColorStateListFromInt(value, key); } - final ConfigurationBoundResourceCache cache = mColorStateListCache; - csl = cache.getInstance(key, theme); - if (csl != null) { - return csl; + ComplexColor complexColor = loadComplexColorFromName(theme, value, id); + if (complexColor != null && complexColor instanceof ColorStateList) { + return (ColorStateList) complexColor; } - final android.content.res.ConstantState factory = - sPreloadedColorStateLists.get(key); + throw new NotFoundException( + "Can't find ColorStateList from drawable resource ID #0x" + + Integer.toHexString(id)); + } + + @NonNull + private ColorStateList getColorStateListFromInt(@NonNull TypedValue value, long key) { + ColorStateList csl; + final android.content.res.ConstantState factory = + sPreloadedComplexColors.get(key); if (factory != null) { - csl = factory.newInstance(this, theme); + return (ColorStateList) factory.newInstance(); } - if (csl == null) { - csl = loadColorStateListForCookie(value, id, theme); - } + csl = ColorStateList.valueOf(value.data); - if (csl != null) { - if (mPreloading) { - if (verifyPreloadConfig(value.changingConfigurations, 0, value.resourceId, - "color")) { - sPreloadedColorStateLists.put(key, csl.getConstantState()); - } - } else { - cache.put(key, theme, csl.getConstantState()); + if (mPreloading) { + if (verifyPreloadConfig(value.changingConfigurations, 0, value.resourceId, + "color")) { + sPreloadedComplexColors.put(key, csl.getConstantState()); } } return csl; } - private ColorStateList loadColorStateListForCookie(TypedValue value, int id, Theme theme) { + /** + * Load a ComplexColor based on the XML file content. The result can be a GradientColor or + * ColorStateList. Note that pure color will be wrapped into a ColorStateList. + * + * We deferred the parser creation to this function b/c we need to differentiate b/t gradient + * and selector tag. + * + * @return a ComplexColor (GradientColor or ColorStateList) based on the XML file content. + */ + @Nullable + private ComplexColor loadComplexColorForCookie(TypedValue value, int id, Theme theme) { if (value.string == null) { throw new UnsupportedOperationException( - "Can't convert to color state list: type=0x" + value.type); + "Can't convert to ComplexColor: type=0x" + value.type); } final String file = value.string.toString(); @@ -2692,29 +2763,45 @@ public class Resources { if ((id >>> 24) == 0x1) { final String name = getResourceName(id); if (name != null) { - Log.d(TAG, "Loading framework color state list #" + Integer.toHexString(id) + Log.d(TAG, "Loading framework ComplexColor #" + Integer.toHexString(id) + ": " + name + " at " + file); } } } if (DEBUG_LOAD) { - Log.v(TAG, "Loading color state list for cookie " + value.assetCookie + ": " + file); + Log.v(TAG, "Loading ComplexColor for cookie " + value.assetCookie + ": " + file); } - final ColorStateList csl; + ComplexColor complexColor = null; Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file); if (file.endsWith(".xml")) { try { - final XmlResourceParser rp = loadXmlResourceParser( - file, id, value.assetCookie, "colorstatelist"); - csl = ColorStateList.createFromXml(this, rp, theme); - rp.close(); + final XmlResourceParser parser = loadXmlResourceParser( + file, id, value.assetCookie, "ComplexColor"); + + final AttributeSet attrs = Xml.asAttributeSet(parser); + int type; + while ((type = parser.next()) != XmlPullParser.START_TAG + && type != XmlPullParser.END_DOCUMENT) { + // Seek parser to start tag. + } + if (type != XmlPullParser.START_TAG) { + throw new XmlPullParserException("No start tag found"); + } + + final String name = parser.getName(); + if (name.equals("gradient")) { + complexColor = GradientColor.createFromXmlInner(this, parser, attrs, theme); + } else if (name.equals("selector")) { + complexColor = ColorStateList.createFromXmlInner(this, parser, attrs, theme); + } + parser.close(); } catch (Exception e) { Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); final NotFoundException rnf = new NotFoundException( - "File " + file + " from color state list resource ID #0x" + "File " + file + " from ComplexColor resource ID #0x" + Integer.toHexString(id)); rnf.initCause(e); throw rnf; @@ -2727,7 +2814,7 @@ public class Resources { } Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); - return csl; + return complexColor; } /** diff --git a/core/java/android/content/res/TypedArray.java b/core/java/android/content/res/TypedArray.java index cc65e1e4f7103..6067577453c9d 100644 --- a/core/java/android/content/res/TypedArray.java +++ b/core/java/android/content/res/TypedArray.java @@ -453,6 +453,39 @@ public class TypedArray { + Integer.toHexString(type)); } + /** + * Retrieve the ComplexColor for the attribute at index. + * The value may be either a {@link android.content.res.ColorStateList} which can wrap a simple + * color value or a {@link android.content.res.GradientColor} + *

+ * This method will return {@code null} if the attribute is not defined or + * is not an integer color, color state list or GradientColor. + * + * @param index Index of attribute to retrieve. + * + * @return ComplexColor for the attribute, or {@code null} if not defined. + * @throws RuntimeException if the attribute if the TypedArray has already + * been recycled. + * @throws UnsupportedOperationException if the attribute is defined but is + * not an integer color, color state list or GradientColor. + */ + @Nullable + public ComplexColor getComplexColor(@StyleableRes int index) { + if (mRecycled) { + throw new RuntimeException("Cannot make calls to a recycled instance!"); + } + + final TypedValue value = mValue; + if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value)) { + if (value.type == TypedValue.TYPE_ATTRIBUTE) { + throw new UnsupportedOperationException( + "Failed to resolve attribute at index " + index + ": " + value); + } + return mResources.loadComplexColor(value, value.resourceId, mTheme); + } + return null; + } + /** * Retrieve the ColorStateList for the attribute at index. * The value may be either a single solid color or a reference to diff --git a/core/java/com/android/internal/util/GrowingArrayUtils.java b/core/java/com/android/internal/util/GrowingArrayUtils.java index b4d2d73066dd7..968d9204b106b 100644 --- a/core/java/com/android/internal/util/GrowingArrayUtils.java +++ b/core/java/com/android/internal/util/GrowingArrayUtils.java @@ -96,6 +96,21 @@ public final class GrowingArrayUtils { return array; } + /** + * Primitive float version of {@link #append(Object[], int, Object)}. + */ + public static float[] append(float[] array, int currentSize, float element) { + assert currentSize <= array.length; + + if (currentSize + 1 > array.length) { + float[] newArray = ArrayUtils.newUnpaddedFloatArray(growSize(currentSize)); + System.arraycopy(array, 0, newArray, 0, currentSize); + array = newArray; + } + array[currentSize] = element; + return array; + } + /** * Inserts an element into the array at the specified index, growing the array if there is no * more room. diff --git a/core/jni/android_graphics_drawable_VectorDrawable.cpp b/core/jni/android_graphics_drawable_VectorDrawable.cpp index 53d4c6a1cb780..563ec8bd98340 100644 --- a/core/jni/android_graphics_drawable_VectorDrawable.cpp +++ b/core/jni/android_graphics_drawable_VectorDrawable.cpp @@ -90,6 +90,18 @@ static void updateFullPathPropertiesAndStrokeStyles(JNIEnv*, jobject, jlong full strokeLineJoin); } +static void updateFullPathFillGradient(JNIEnv*, jobject, jlong pathPtr, jlong fillGradientPtr) { + VectorDrawable::FullPath* path = reinterpret_cast(pathPtr); + SkShader* fillShader = reinterpret_cast(fillGradientPtr); + path->setFillGradient(fillShader); +} + +static void updateFullPathStrokeGradient(JNIEnv*, jobject, jlong pathPtr, jlong strokeGradientPtr) { + VectorDrawable::FullPath* path = reinterpret_cast(pathPtr); + SkShader* strokeShader = reinterpret_cast(strokeGradientPtr); + path->setStrokeGradient(strokeShader); +} + static jboolean getFullPathProperties(JNIEnv* env, jobject, jlong fullPathPtr, jbyteArray outProperties, jint length) { VectorDrawable::FullPath* fullPath = reinterpret_cast(fullPathPtr); @@ -331,6 +343,8 @@ static const JNINativeMethod gMethods[] = { {"nCreateFullPath", "!()J", (void*)createEmptyFullPath}, {"nCreateFullPath", "!(J)J", (void*)createFullPath}, {"nUpdateFullPathProperties", "!(JFIFIFFFFFII)V", (void*)updateFullPathPropertiesAndStrokeStyles}, + {"nUpdateFullPathFillGradient", "!(JJ)V", (void*)updateFullPathFillGradient}, + {"nUpdateFullPathStrokeGradient", "!(JJ)V", (void*)updateFullPathStrokeGradient}, {"nGetFullPathProperties", "(J[BI)Z", (void*)getFullPathProperties}, {"nGetGroupProperties", "(J[FI)Z", (void*)getGroupProperties}, diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 713038b60d89d..1f7206eeecee4 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -3727,7 +3727,7 @@ i - @@ -3740,7 +3740,7 @@ i - @@ -8135,4 +8135,52 @@ i + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 57132ea82977a..a2ad09b248c3c 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -2691,6 +2691,11 @@ + + + + + diff --git a/graphics/java/android/graphics/Shader.java b/graphics/java/android/graphics/Shader.java index adb282fb4bf95..94983b3d1f514 100644 --- a/graphics/java/android/graphics/Shader.java +++ b/graphics/java/android/graphics/Shader.java @@ -117,7 +117,10 @@ public class Shader { } } - /* package */ long getNativeInstance() { + /** + * @hide + */ + public long getNativeInstance() { return native_instance; } diff --git a/graphics/java/android/graphics/drawable/VectorDrawable.java b/graphics/java/android/graphics/drawable/VectorDrawable.java index 65260210042c2..1fc1b83f7a6c0 100644 --- a/graphics/java/android/graphics/drawable/VectorDrawable.java +++ b/graphics/java/android/graphics/drawable/VectorDrawable.java @@ -17,6 +17,8 @@ package android.graphics.drawable; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.res.ColorStateList; +import android.content.res.ComplexColor; +import android.content.res.GradientColor; import android.content.res.Resources; import android.content.res.Resources.Theme; import android.content.res.TypedArray; @@ -27,6 +29,7 @@ import android.graphics.PixelFormat; import android.graphics.PorterDuffColorFilter; import android.graphics.Rect; import android.graphics.PorterDuff.Mode; +import android.graphics.Shader; import android.util.ArrayMap; import android.util.AttributeSet; import android.util.DisplayMetrics; @@ -121,9 +124,9 @@ import java.util.Stack; *

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
- *
Specifies the color used to fill the path. May be a color or (SDK 24+ only) a color state - * list. If this property is animated, any value set by the animation will override the original - * value. No path fill is drawn if this property is not specified.
+ *
Specifies the color used to fill the path. May be a color, also may be a color state list or + * a gradient color for SDK 24+. If this property is animated, any value set by the animation will + * override the original value. No path fill is drawn if this property is not specified.
*
android:strokeColor
*
Specifies the color used to draw the path outline. May be a color or (SDK 24+ only) a color * state list. If this property is animated, any value set by the animation will override the @@ -1276,8 +1279,9 @@ public class VectorDrawable extends Drawable { ///////////////////////////////////////////////////// // Variables below need to be copied (deep copy if applicable) for mutation. private int[] mThemeAttrs; - ColorStateList mStrokeColors = null; - ColorStateList mFillColors = null; + + ComplexColor mStrokeColors = null; + ComplexColor mFillColors = null; private long mNativePtr = 0; public VFullPath() { @@ -1297,23 +1301,25 @@ public class VectorDrawable extends Drawable { public boolean onStateChange(int[] stateSet) { boolean changed = false; - if (mStrokeColors != null) { + if (mStrokeColors != null && mStrokeColors instanceof ColorStateList) { final int oldStrokeColor = getStrokeColor(); - final int newStrokeColor = mStrokeColors.getColorForState(stateSet, oldStrokeColor); + final int newStrokeColor = + ((ColorStateList) mStrokeColors).getColorForState(stateSet, oldStrokeColor); changed |= oldStrokeColor != newStrokeColor; if (oldStrokeColor != newStrokeColor) { nSetStrokeColor(mNativePtr, newStrokeColor); } } - if (mFillColors != null) { + if (mFillColors != null && mFillColors instanceof ColorStateList) { final int oldFillColor = getFillColor(); - final int newFillColor = mFillColors.getColorForState(stateSet, oldFillColor); + final int newFillColor = ((ColorStateList) mFillColors).getColorForState(stateSet, oldFillColor); changed |= oldFillColor != newFillColor; if (oldFillColor != newFillColor) { nSetFillColor(mNativePtr, newFillColor); } } + return changed; } @@ -1372,7 +1378,8 @@ public class VectorDrawable extends Drawable { int strokeLineCap = properties.getInt(STROKE_LINE_CAP_INDEX * 4); int strokeLineJoin = properties.getInt(STROKE_LINE_JOIN_INDEX * 4); float strokeMiterLimit = properties.getFloat(STROKE_MITER_LIMIT_INDEX * 4); - + Shader fillGradient = null; + Shader strokeGradient = null; // Account for any configuration changes. mChangingConfigurations |= a.getChangingConfigurations(); @@ -1391,23 +1398,43 @@ public class VectorDrawable extends Drawable { nSetPathString(mNativePtr, pathString, pathString.length()); } - final ColorStateList fillColors = a.getColorStateList( + final ComplexColor fillColors = a.getComplexColor( R.styleable.VectorDrawablePath_fillColor); if (fillColors != null) { - // If the color state list isn't stateful, discard the state - // list and keep the default (e.g. the only) color. - mFillColors = fillColors.isStateful() ? fillColors : null; + // If the colors is a gradient color, or the color state list is stateful, keep the + // colors information. Otherwise, discard the colors and keep the default color. + if (fillColors instanceof GradientColor) { + mFillColors = fillColors; + fillGradient = ((GradientColor) fillColors).getShader(); + } else if (fillColors.isStateful()) { + mFillColors = fillColors; + } else { + mFillColors = null; + } fillColor = fillColors.getDefaultColor(); } - final ColorStateList strokeColors = a.getColorStateList( + final ComplexColor strokeColors = a.getComplexColor( R.styleable.VectorDrawablePath_strokeColor); if (strokeColors != null) { - // If the color state list isn't stateful, discard the state - // list and keep the default (e.g. the only) color. - mStrokeColors = strokeColors.isStateful() ? strokeColors : null; + // If the colors is a gradient color, or the color state list is stateful, keep the + // colors information. Otherwise, discard the colors and keep the default color. + if (strokeColors instanceof GradientColor) { + mStrokeColors = strokeColors; + strokeGradient = ((GradientColor) strokeColors).getShader(); + } else if (strokeColors.isStateful()) { + mStrokeColors = strokeColors; + } else { + mStrokeColors = null; + } strokeColor = strokeColors.getDefaultColor(); } + // Update the gradient info, even if the gradiet is null. + nUpdateFullPathFillGradient(mNativePtr, + fillGradient != null ? fillGradient.getNativeInstance() : 0); + nUpdateFullPathStrokeGradient(mNativePtr, + strokeGradient != null ? strokeGradient.getNativeInstance() : 0); + fillAlpha = a.getFloat(R.styleable.VectorDrawablePath_fillAlpha, fillAlpha); strokeLineCap = a.getInt( @@ -1434,18 +1461,44 @@ public class VectorDrawable extends Drawable { @Override public boolean canApplyTheme() { - return mThemeAttrs != null; + if (mThemeAttrs != null) { + return true; + } + boolean fillCanApplyTheme = canGradientApplyTheme(mFillColors); + boolean strokeCanApplyTheme = canGradientApplyTheme(mStrokeColors); + if (fillCanApplyTheme || strokeCanApplyTheme) { + return true; + } + return false; + } @Override public void applyTheme(Theme t) { - if (mThemeAttrs == null) { - return; + if (mThemeAttrs != null) { + final TypedArray a = t.resolveAttributes(mThemeAttrs, R.styleable.VectorDrawablePath); + updateStateFromTypedArray(a); + a.recycle(); } - final TypedArray a = t.resolveAttributes(mThemeAttrs, R.styleable.VectorDrawablePath); - updateStateFromTypedArray(a); - a.recycle(); + boolean fillCanApplyTheme = canGradientApplyTheme(mFillColors); + boolean strokeCanApplyTheme = canGradientApplyTheme(mStrokeColors); + if (fillCanApplyTheme) { + mFillColors = mFillColors.obtainForTheme(t); + nUpdateFullPathFillGradient(mNativePtr, + ((GradientColor)mFillColors).getShader().getNativeInstance()); + } + + if (strokeCanApplyTheme) { + mStrokeColors = mStrokeColors.obtainForTheme(t); + nUpdateFullPathStrokeGradient(mNativePtr, + ((GradientColor)mStrokeColors).getShader().getNativeInstance()); + } + } + + private boolean canGradientApplyTheme(ComplexColor complexColor) { + return complexColor != null && complexColor.canApplyTheme() + && complexColor instanceof GradientColor; } /* Setters and Getters, used by animator from AnimatedVectorDrawable. */ @@ -1560,6 +1613,8 @@ public class VectorDrawable extends Drawable { int strokeColor, float strokeAlpha, int fillColor, float fillAlpha, float trimPathStart, float trimPathEnd, float trimPathOffset, float strokeMiterLimit, int strokeLineCap, int strokeLineJoin); + private static native void nUpdateFullPathFillGradient(long pathPtr, long fillGradientPtr); + private static native void nUpdateFullPathStrokeGradient(long pathPtr, long strokeGradientPtr); private static native long nCreateClipPath(); private static native long nCreateClipPath(long clipPathPtr); diff --git a/libs/hwui/VectorDrawable.cpp b/libs/hwui/VectorDrawable.cpp index 1d31c9e08a9e3..4d4acb94c2559 100644 --- a/libs/hwui/VectorDrawable.cpp +++ b/libs/hwui/VectorDrawable.cpp @@ -18,6 +18,7 @@ #include "PathParser.h" #include "SkImageInfo.h" +#include "SkShader.h" #include #include "utils/Macros.h" #include "utils/VectorDrawableUtils.h" @@ -49,7 +50,7 @@ void Path::draw(SkCanvas* outCanvas, const SkMatrix& groupStackedMatrix, float s float minScale = fmin(scaleX, scaleY); float strokeScale = minScale * matrixScale; - drawPath(outCanvas, renderPath, strokeScale); + drawPath(outCanvas, renderPath, strokeScale, pathMatrix); } void Path::setPathData(const Data& data) { @@ -148,6 +149,9 @@ FullPath::FullPath(const FullPath& path) : Path(path) { mStrokeMiterLimit = path.mStrokeMiterLimit; mStrokeLineCap = path.mStrokeLineCap; mStrokeLineJoin = path.mStrokeLineJoin; + + SkRefCnt_SafeAssign(mStrokeGradient, path.mStrokeGradient); + SkRefCnt_SafeAssign(mFillGradient, path.mFillGradient); } const SkPath& FullPath::getUpdatedPath() { @@ -186,22 +190,44 @@ inline SkColor applyAlpha(SkColor color, float alpha) { return SkColorSetA(color, alphaBytes * alpha); } -void FullPath::drawPath(SkCanvas* outCanvas, const SkPath& renderPath, float strokeScale){ - // Draw path's fill, if fill color isn't transparent. - if (mFillColor != SK_ColorTRANSPARENT) { - mPaint.setStyle(SkPaint::Style::kFill_Style); - mPaint.setAntiAlias(true); +void FullPath::drawPath(SkCanvas* outCanvas, const SkPath& renderPath, float strokeScale, + const SkMatrix& matrix){ + // Draw path's fill, if fill color or gradient is valid + bool needsFill = false; + if (mFillGradient != nullptr) { + mPaint.setColor(applyAlpha(SK_ColorBLACK, mFillAlpha)); + SkShader* newShader = mFillGradient->newWithLocalMatrix(matrix); + mPaint.setShader(newShader); + needsFill = true; + } else if (mFillColor != SK_ColorTRANSPARENT) { mPaint.setColor(applyAlpha(mFillColor, mFillAlpha)); outCanvas->drawPath(renderPath, mPaint); + needsFill = true; } - // Draw path's stroke, if stroke color isn't transparent - if (mStrokeColor != SK_ColorTRANSPARENT) { + + if (needsFill) { + mPaint.setStyle(SkPaint::Style::kFill_Style); + mPaint.setAntiAlias(true); + outCanvas->drawPath(renderPath, mPaint); + } + + // Draw path's stroke, if stroke color or gradient is valid + bool needsStroke = false; + if (mStrokeGradient != nullptr) { + mPaint.setColor(applyAlpha(SK_ColorBLACK, mStrokeAlpha)); + SkShader* newShader = mStrokeGradient->newWithLocalMatrix(matrix); + mPaint.setShader(newShader); + needsStroke = true; + } else if (mStrokeColor != SK_ColorTRANSPARENT) { + mPaint.setColor(applyAlpha(mStrokeColor, mStrokeAlpha)); + needsStroke = true; + } + if (needsStroke) { mPaint.setStyle(SkPaint::Style::kStroke_Style); mPaint.setAntiAlias(true); mPaint.setStrokeJoin(mStrokeLineJoin); mPaint.setStrokeCap(mStrokeLineCap); mPaint.setStrokeMiter(mStrokeMiterLimit); - mPaint.setColor(applyAlpha(mStrokeColor, mStrokeAlpha)); mPaint.setStrokeWidth(mStrokeWidth * strokeScale); outCanvas->drawPath(renderPath, mPaint); } @@ -288,7 +314,7 @@ bool FullPath::getProperties(int8_t* outProperties, int length) { } void ClipPath::drawPath(SkCanvas* outCanvas, const SkPath& renderPath, - float strokeScale){ + float strokeScale, const SkMatrix& matrix){ outCanvas->clipPath(renderPath, SkRegion::kIntersect_Op); } diff --git a/libs/hwui/VectorDrawable.h b/libs/hwui/VectorDrawable.h index 5ae5f6a3bdba1..09bdce596a218 100644 --- a/libs/hwui/VectorDrawable.h +++ b/libs/hwui/VectorDrawable.h @@ -26,6 +26,7 @@ #include #include #include +#include #include #include @@ -95,7 +96,7 @@ public: protected: virtual const SkPath& getUpdatedPath(); virtual void drawPath(SkCanvas *outCanvas, const SkPath& renderPath, - float strokeScale) = 0; + float strokeScale, const SkMatrix& matrix) = 0; Data mData; SkPath mSkPath; bool mSkPathDirty = true; @@ -108,6 +109,11 @@ public: FullPath() : Path() {} FullPath(const Data& nodes) : Path(nodes) {} + ~FullPath() { + SkSafeUnref(mFillGradient); + SkSafeUnref(mStrokeGradient); + } + void updateProperties(float strokeWidth, SkColor strokeColor, float strokeAlpha, SkColor fillColor, float fillAlpha, float trimPathStart, float trimPathEnd, float trimPathOffset, @@ -162,10 +168,18 @@ public: } bool getProperties(int8_t* outProperties, int length); + void setFillGradient(SkShader* fillGradient) { + SkRefCnt_SafeAssign(mFillGradient, fillGradient); + }; + void setStrokeGradient(SkShader* strokeGradient) { + SkRefCnt_SafeAssign(mStrokeGradient, strokeGradient); + }; + + protected: const SkPath& getUpdatedPath() override; void drawPath(SkCanvas* outCanvas, const SkPath& renderPath, - float strokeScale) override; + float strokeScale, const SkMatrix& matrix) override; private: // Applies trimming to the specified path. @@ -174,6 +188,8 @@ private: SkColor mStrokeColor = SK_ColorTRANSPARENT; float mStrokeAlpha = 1; SkColor mFillColor = SK_ColorTRANSPARENT; + SkShader* mStrokeGradient = nullptr; + SkShader* mFillGradient = nullptr; float mFillAlpha = 1; float mTrimPathStart = 0; float mTrimPathEnd = 1; @@ -195,7 +211,7 @@ public: protected: void drawPath(SkCanvas* outCanvas, const SkPath& renderPath, - float strokeScale) override; + float strokeScale, const SkMatrix& matrix) override; }; class ANDROID_API Group: public Node { diff --git a/tests/VectorDrawableTest/res/color/fill_gradient_linear.xml b/tests/VectorDrawableTest/res/color/fill_gradient_linear.xml new file mode 100644 index 0000000000000..e0e3f03d64f59 --- /dev/null +++ b/tests/VectorDrawableTest/res/color/fill_gradient_linear.xml @@ -0,0 +1,29 @@ + + + + \ No newline at end of file diff --git a/tests/VectorDrawableTest/res/color/fill_gradient_linear_item.xml b/tests/VectorDrawableTest/res/color/fill_gradient_linear_item.xml new file mode 100644 index 0000000000000..cfb123603735b --- /dev/null +++ b/tests/VectorDrawableTest/res/color/fill_gradient_linear_item.xml @@ -0,0 +1,32 @@ + + + + + + + \ No newline at end of file diff --git a/tests/VectorDrawableTest/res/color/fill_gradient_linear_item_overlap.xml b/tests/VectorDrawableTest/res/color/fill_gradient_linear_item_overlap.xml new file mode 100644 index 0000000000000..18274b9ec55a4 --- /dev/null +++ b/tests/VectorDrawableTest/res/color/fill_gradient_linear_item_overlap.xml @@ -0,0 +1,33 @@ + + + + + + + + \ No newline at end of file diff --git a/tests/VectorDrawableTest/res/color/fill_gradient_radial.xml b/tests/VectorDrawableTest/res/color/fill_gradient_radial.xml new file mode 100644 index 0000000000000..ef6fd70c67f7d --- /dev/null +++ b/tests/VectorDrawableTest/res/color/fill_gradient_radial.xml @@ -0,0 +1,27 @@ + + + + \ No newline at end of file diff --git a/tests/VectorDrawableTest/res/color/fill_gradient_radial_item.xml b/tests/VectorDrawableTest/res/color/fill_gradient_radial_item.xml new file mode 100644 index 0000000000000..51b0e173f7467 --- /dev/null +++ b/tests/VectorDrawableTest/res/color/fill_gradient_radial_item.xml @@ -0,0 +1,30 @@ + + + + + + + \ No newline at end of file diff --git a/tests/VectorDrawableTest/res/color/fill_gradient_radial_item_short.xml b/tests/VectorDrawableTest/res/color/fill_gradient_radial_item_short.xml new file mode 100644 index 0000000000000..8caa1b4348a28 --- /dev/null +++ b/tests/VectorDrawableTest/res/color/fill_gradient_radial_item_short.xml @@ -0,0 +1,26 @@ + + + + + + \ No newline at end of file diff --git a/tests/VectorDrawableTest/res/color/fill_gradient_sweep.xml b/tests/VectorDrawableTest/res/color/fill_gradient_sweep.xml new file mode 100644 index 0000000000000..e1fbd10b7e91a --- /dev/null +++ b/tests/VectorDrawableTest/res/color/fill_gradient_sweep.xml @@ -0,0 +1,27 @@ + + + + diff --git a/tests/VectorDrawableTest/res/color/fill_gradient_sweep_item.xml b/tests/VectorDrawableTest/res/color/fill_gradient_sweep_item.xml new file mode 100644 index 0000000000000..332b938949607 --- /dev/null +++ b/tests/VectorDrawableTest/res/color/fill_gradient_sweep_item.xml @@ -0,0 +1,30 @@ + + + + + + + diff --git a/tests/VectorDrawableTest/res/color/fill_gradient_sweep_item_long.xml b/tests/VectorDrawableTest/res/color/fill_gradient_sweep_item_long.xml new file mode 100644 index 0000000000000..3931288c5c25d --- /dev/null +++ b/tests/VectorDrawableTest/res/color/fill_gradient_sweep_item_long.xml @@ -0,0 +1,30 @@ + + + + + + + + + + diff --git a/tests/VectorDrawableTest/res/color/stroke_gradient.xml b/tests/VectorDrawableTest/res/color/stroke_gradient.xml new file mode 100644 index 0000000000000..cb324c9a7f4e8 --- /dev/null +++ b/tests/VectorDrawableTest/res/color/stroke_gradient.xml @@ -0,0 +1,29 @@ + + + + \ No newline at end of file diff --git a/tests/VectorDrawableTest/res/color/stroke_gradient_item.xml b/tests/VectorDrawableTest/res/color/stroke_gradient_item.xml new file mode 100644 index 0000000000000..15d948c25899f --- /dev/null +++ b/tests/VectorDrawableTest/res/color/stroke_gradient_item.xml @@ -0,0 +1,32 @@ + + + + + + + \ No newline at end of file diff --git a/tests/VectorDrawableTest/res/color/stroke_gradient_item_alpha.xml b/tests/VectorDrawableTest/res/color/stroke_gradient_item_alpha.xml new file mode 100644 index 0000000000000..fda2b88bc3e12 --- /dev/null +++ b/tests/VectorDrawableTest/res/color/stroke_gradient_item_alpha.xml @@ -0,0 +1,28 @@ + + + + + + + \ No newline at end of file diff --git a/tests/VectorDrawableTest/res/color/vector_icon_fill_state_list.xml b/tests/VectorDrawableTest/res/color/vector_icon_fill_state_list.xml new file mode 100644 index 0000000000000..45d88b567ba26 --- /dev/null +++ b/tests/VectorDrawableTest/res/color/vector_icon_fill_state_list.xml @@ -0,0 +1,22 @@ + + + + + + \ No newline at end of file diff --git a/tests/VectorDrawableTest/res/color/vector_icon_stroke_state_list.xml b/tests/VectorDrawableTest/res/color/vector_icon_stroke_state_list.xml new file mode 100644 index 0000000000000..16251c8d50bde --- /dev/null +++ b/tests/VectorDrawableTest/res/color/vector_icon_stroke_state_list.xml @@ -0,0 +1,22 @@ + + + + + + \ No newline at end of file diff --git a/tests/VectorDrawableTest/res/drawable/vector_drawable01.xml b/tests/VectorDrawableTest/res/drawable/vector_drawable01.xml index 2be99bed4bfb8..89afde22f6355 100644 --- a/tests/VectorDrawableTest/res/drawable/vector_drawable01.xml +++ b/tests/VectorDrawableTest/res/drawable/vector_drawable01.xml @@ -27,8 +27,8 @@ diff --git a/tests/VectorDrawableTest/res/drawable/vector_icon_gradient_1.xml b/tests/VectorDrawableTest/res/drawable/vector_icon_gradient_1.xml new file mode 100644 index 0000000000000..d67aca7cdaec9 --- /dev/null +++ b/tests/VectorDrawableTest/res/drawable/vector_icon_gradient_1.xml @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/VectorDrawableTest/res/drawable/vector_icon_gradient_2.xml b/tests/VectorDrawableTest/res/drawable/vector_icon_gradient_2.xml new file mode 100644 index 0000000000000..abf3c7a86b80d --- /dev/null +++ b/tests/VectorDrawableTest/res/drawable/vector_icon_gradient_2.xml @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/VectorDrawableTest/res/drawable/vector_icon_gradient_3.xml b/tests/VectorDrawableTest/res/drawable/vector_icon_gradient_3.xml new file mode 100644 index 0000000000000..5f9726f72c034 --- /dev/null +++ b/tests/VectorDrawableTest/res/drawable/vector_icon_gradient_3.xml @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/VectorDrawableTest/res/drawable/vector_icon_state_list.xml b/tests/VectorDrawableTest/res/drawable/vector_icon_state_list.xml new file mode 100644 index 0000000000000..b1ed85025040c --- /dev/null +++ b/tests/VectorDrawableTest/res/drawable/vector_icon_state_list.xml @@ -0,0 +1,31 @@ + + + + + + + \ No newline at end of file diff --git a/tests/VectorDrawableTest/src/com/android/test/dynamic/VectorDrawablePerformance.java b/tests/VectorDrawableTest/src/com/android/test/dynamic/VectorDrawablePerformance.java index b4a93f68f6c99..a7da286ac6ab6 100644 --- a/tests/VectorDrawableTest/src/com/android/test/dynamic/VectorDrawablePerformance.java +++ b/tests/VectorDrawableTest/src/com/android/test/dynamic/VectorDrawablePerformance.java @@ -35,6 +35,10 @@ import java.text.DecimalFormat; public class VectorDrawablePerformance extends Activity { private static final String LOGCAT = "VectorDrawable1"; protected int[] icon = { + R.drawable.vector_icon_gradient_1, + R.drawable.vector_icon_gradient_2, + R.drawable.vector_icon_gradient_3, + R.drawable.vector_icon_state_list, R.drawable.vector_drawable01, R.drawable.vector_drawable02, R.drawable.vector_drawable03, @@ -102,7 +106,7 @@ public class VectorDrawablePerformance extends Activity { ScrollView scrollView = new ScrollView(this); GridLayout container = new GridLayout(this); scrollView.addView(container); - container.setColumnCount(5); + container.setColumnCount(4); Resources res = this.getResources(); container.setBackgroundColor(0xFF888888); VectorDrawable []d = new VectorDrawable[icon.length];