diff options
author | Alan Viverette <alanv@google.com> | 2014-03-24 18:00:26 -0700 |
---|---|---|
committer | Alan Viverette <alanv@google.com> | 2014-03-24 18:00:26 -0700 |
commit | 52b999f0721b53e9c6e18a4bd664e89aeb65b2d5 (patch) | |
tree | 3ef7369c91bd51fae75769c23ebfb71e04d3989c /graphics | |
parent | 852472d9aa39eb4591eac43487ac3e0944e1daf6 (diff) | |
download | frameworks_base-52b999f0721b53e9c6e18a4bd664e89aeb65b2d5.zip frameworks_base-52b999f0721b53e9c6e18a4bd664e89aeb65b2d5.tar.gz frameworks_base-52b999f0721b53e9c6e18a4bd664e89aeb65b2d5.tar.bz2 |
Implement APIs for obtaining, caching themed Drawables
When Drawables are inflated during preload (or otherwise without a theme)
they cache their themeable attributes in their constant state as an array
keyed on attribute index. Drawables inflated with a theme will simply
resolve theme attributes as part of normal inflation, and they will not
cache any themeable attributes.
Drawables obtained from Resources are pulled from theme-specific cache
when possible. If an unthemed Drawable exists in the preload cache, a
new constant state will be obtained for the Drawable and the theme will
be applied by resolving the cached themeable attributes and overwriting
their respective constant state properties. If no cached version exists,
a new Drawable is inflated against the desired theme.
Constant states from themed drawables may be cached if the applied theme
is "pure" and was loaded from a style resource without any subsequent
modifications.
This CL does not handle applying themes to several Drawable types, but it
fully supports BitmapDrawable, GradientDrawable, NinePatchDrawable,
ColorDrawable, and TouchFeedbackDrawable.
BUG: 12611005
Change-Id: I4e794fbb62f7a371715f4ebdf946ee5f9a5ad1c9
Diffstat (limited to 'graphics')
19 files changed, 1642 insertions, 565 deletions
diff --git a/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java b/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java index 9accbbc..a37ceef 100644 --- a/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java +++ b/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java @@ -21,10 +21,12 @@ import android.graphics.Rect; import android.graphics.ColorFilter; import android.content.res.Resources; import android.content.res.TypedArray; +import android.content.res.Resources.Theme; import android.util.AttributeSet; import android.util.TypedValue; import android.util.Log; import android.os.SystemClock; + import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -223,7 +225,7 @@ public class AnimatedRotateDrawable extends Drawable implements Drawable.Callbac } @Override - public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs) + public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) throws XmlPullParserException, IOException { final TypedArray a = r.obtainAttributes(attrs, R.styleable.AnimatedRotateDrawable); @@ -258,7 +260,7 @@ public class AnimatedRotateDrawable extends Drawable implements Drawable.Callbac continue; } - if ((drawable = Drawable.createFromXmlInner(r, parser, attrs)) == null) { + if ((drawable = Drawable.createFromXmlInnerThemed(r, parser, attrs, theme)) == null) { Log.w("drawable", "Bad element under <animated-rotate>: " + parser .getName()); } diff --git a/graphics/java/android/graphics/drawable/AnimationDrawable.java b/graphics/java/android/graphics/drawable/AnimationDrawable.java index 02b2588..3f94e26 100644 --- a/graphics/java/android/graphics/drawable/AnimationDrawable.java +++ b/graphics/java/android/graphics/drawable/AnimationDrawable.java @@ -23,6 +23,7 @@ import org.xmlpull.v1.XmlPullParserException; import android.content.res.Resources; import android.content.res.TypedArray; +import android.content.res.Resources.Theme; import android.os.SystemClock; import android.util.AttributeSet; @@ -236,7 +237,7 @@ public class AnimationDrawable extends DrawableContainer implements Runnable, An } @Override - public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs) + public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) throws XmlPullParserException, IOException { TypedArray a = r.obtainAttributes(attrs, @@ -292,7 +293,7 @@ public class AnimationDrawable extends DrawableContainer implements Runnable, An ": <item> tag requires a 'drawable' attribute or child tag" + " defining a drawable"); } - dr = Drawable.createFromXmlInner(r, parser, attrs); + dr = Drawable.createFromXmlInnerThemed(r, parser, attrs, theme); } mAnimationState.addFrame(dr, duration); diff --git a/graphics/java/android/graphics/drawable/BitmapDrawable.java b/graphics/java/android/graphics/drawable/BitmapDrawable.java index fe08f4b..19131f2 100644 --- a/graphics/java/android/graphics/drawable/BitmapDrawable.java +++ b/graphics/java/android/graphics/drawable/BitmapDrawable.java @@ -18,6 +18,7 @@ package android.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.BitmapFactory; @@ -37,6 +38,8 @@ import android.util.DisplayMetrics; import android.util.LayoutDirection; import android.view.Gravity; +import com.android.internal.R; + import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -114,7 +117,7 @@ public class BitmapDrawable extends Drawable { */ @Deprecated public BitmapDrawable(Bitmap bitmap) { - this(new BitmapState(bitmap), null); + this(new BitmapState(bitmap), null, null); } /** @@ -122,7 +125,7 @@ public class BitmapDrawable extends Drawable { * the display metrics of the resources. */ public BitmapDrawable(Resources res, Bitmap bitmap) { - this(new BitmapState(bitmap), res); + this(new BitmapState(bitmap), res, null); mBitmapState.mTargetDensity = mTargetDensity; } @@ -133,7 +136,7 @@ public class BitmapDrawable extends Drawable { */ @Deprecated public BitmapDrawable(String filepath) { - this(new BitmapState(BitmapFactory.decodeFile(filepath)), null); + this(new BitmapState(BitmapFactory.decodeFile(filepath)), null, null); if (mBitmap == null) { android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + filepath); } @@ -144,7 +147,7 @@ public class BitmapDrawable extends Drawable { */ @SuppressWarnings({"UnusedParameters"}) public BitmapDrawable(Resources res, String filepath) { - this(new BitmapState(BitmapFactory.decodeFile(filepath)), null); + this(new BitmapState(BitmapFactory.decodeFile(filepath)), null, null); mBitmapState.mTargetDensity = mTargetDensity; if (mBitmap == null) { android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + filepath); @@ -158,7 +161,7 @@ public class BitmapDrawable extends Drawable { */ @Deprecated public BitmapDrawable(java.io.InputStream is) { - this(new BitmapState(BitmapFactory.decodeStream(is)), null); + this(new BitmapState(BitmapFactory.decodeStream(is)), null, null); if (mBitmap == null) { android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + is); } @@ -169,7 +172,7 @@ public class BitmapDrawable extends Drawable { */ @SuppressWarnings({"UnusedParameters"}) public BitmapDrawable(Resources res, java.io.InputStream is) { - this(new BitmapState(BitmapFactory.decodeStream(is)), null); + this(new BitmapState(BitmapFactory.decodeStream(is)), null, null); mBitmapState.mTargetDensity = mTargetDensity; if (mBitmap == null) { android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + is); @@ -633,6 +636,7 @@ public class BitmapDrawable extends Drawable { /** * @hide Candidate for future API inclusion */ + @Override public void setXfermode(Xfermode xfermode) { mBitmapState.mPaint.setXfermode(xfermode); invalidateSelf(); @@ -676,64 +680,228 @@ public class BitmapDrawable extends Drawable { } @Override - public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs) + public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) throws XmlPullParserException, IOException { - super.inflate(r, parser, attrs); + super.inflate(r, parser, attrs, theme); + + final TypedArray a = obtainAttributes( + r, theme, attrs, R.styleable.BitmapDrawable); + inflateStateFromTypedArray(a); + a.recycle(); + } + /** + * Initializes the constant state from the values in the typed array. + */ + private void inflateStateFromTypedArray(TypedArray a) throws XmlPullParserException { + final Resources r = a.getResources(); final BitmapState state = mBitmapState; - final TypedArray a = r.obtainAttributes(attrs, com.android.internal.R.styleable.BitmapDrawable); - final int id = a.getResourceId(com.android.internal.R.styleable.BitmapDrawable_src, 0); - if (id == 0) { - throw new XmlPullParserException(parser.getPositionDescription() + - ": <bitmap> requires a valid src attribute"); + // Extract the theme attributes, if any. + final int[] themeAttrs = a.extractThemeAttrs(); + state.mThemeAttrs = themeAttrs; + + if (themeAttrs == null || themeAttrs[R.styleable.BitmapDrawable_src] == 0) { + final int id = a.getResourceId(R.styleable.BitmapDrawable_src, 0); + if (id == 0) { + throw new XmlPullParserException(a.getPositionDescription() + + ": <bitmap> requires a valid src attribute"); + } + + final Bitmap bitmap = BitmapFactory.decodeResource(r, id); + if (bitmap == null) { + throw new XmlPullParserException(a.getPositionDescription() + + ": <bitmap> requires a valid src attribute"); + } + state.mBitmap = bitmap; + setBitmap(bitmap); } - final Bitmap bitmap = BitmapFactory.decodeResource(r, id); - if (bitmap == null) { - throw new XmlPullParserException(parser.getPositionDescription() + - ": <bitmap> requires a valid src attribute"); + + setTargetDensity(r.getDisplayMetrics()); + + if (themeAttrs == null || themeAttrs[R.styleable.BitmapDrawable_mipMap] == 0) { + final boolean defMipMap = state.mBitmap != null ? state.mBitmap.hasMipMap() : false; + final boolean mipMap = a.getBoolean( + R.styleable.BitmapDrawable_mipMap, defMipMap); + setMipMap(mipMap); } - state.mBitmap = bitmap; - setBitmap(bitmap); + + if (themeAttrs == null || themeAttrs[R.styleable.BitmapDrawable_autoMirrored] == 0) { + final boolean autoMirrored = a.getBoolean( + R.styleable.BitmapDrawable_autoMirrored, false); + setAutoMirrored(autoMirrored); + } + + if (themeAttrs == null || themeAttrs[R.styleable.BitmapDrawable_tintMode] == 0) { + final int tintModeValue = a.getInt( + R.styleable.BitmapDrawable_tintMode, -1); + state.mTintMode = Drawable.parseTintMode(tintModeValue, Mode.SRC_IN); + } + + if (themeAttrs == null || themeAttrs[R.styleable.BitmapDrawable_tint] == 0) { + state.mTint = a.getColorStateList(R.styleable.BitmapDrawable_tint); + if (state.mTint != null) { + final int color = state.mTint.getColorForState(getState(), 0); + mTintFilter = new PorterDuffColorFilter(color, mBitmapState.mTintMode); + } + } + + final Paint paint = mBitmapState.mPaint; + + if (themeAttrs == null || themeAttrs[R.styleable.BitmapDrawable_antialias] == 0) { + final boolean antiAlias = a.getBoolean( + R.styleable.BitmapDrawable_antialias, paint.isAntiAlias()); + paint.setAntiAlias(antiAlias); + } + + if (themeAttrs == null || themeAttrs[R.styleable.BitmapDrawable_filter] == 0) { + final boolean filter = a.getBoolean( + R.styleable.BitmapDrawable_filter, paint.isFilterBitmap()); + paint.setFilterBitmap(filter); + } + + if (themeAttrs == null || themeAttrs[R.styleable.BitmapDrawable_dither] == 0) { + final boolean dither = a.getBoolean( + R.styleable.BitmapDrawable_dither, paint.isDither()); + paint.setDither(dither); + } + + if (themeAttrs == null || themeAttrs[R.styleable.BitmapDrawable_gravity] == 0) { + final int gravity = a.getInt( + R.styleable.BitmapDrawable_gravity, Gravity.FILL); + setGravity(gravity); + } + + if (themeAttrs == null || themeAttrs[R.styleable.BitmapDrawable_tileMode] == 0) { + final int tileMode = a.getInt( + R.styleable.BitmapDrawable_tileMode, -1); + setTileModeInternal(tileMode); + } + } + + @Override + public void applyTheme(Theme t) { + super.applyTheme(t); + + final BitmapState state = mBitmapState; + if (state == null) { + throw new RuntimeException("Can't apply theme to <bitmap> with no constant state"); + } + + final int[] themeAttrs = state.mThemeAttrs; + if (themeAttrs != null) { + final TypedArray a = t.resolveAttributes(themeAttrs, R.styleable.BitmapDrawable, 0, 0); + updateStateFromTypedArray(a); + a.recycle(); + } + } + + /** + * Updates the constant state from the values in the typed array. + */ + private void updateStateFromTypedArray(TypedArray a) { + final Resources r = a.getResources(); + final BitmapState state = mBitmapState; + final Paint paint = mBitmapState.mPaint; + + if (a.hasValue(R.styleable.BitmapDrawable_antialias)) { + final boolean antiAlias = a.getBoolean( + R.styleable.BitmapDrawable_antialias, paint.isAntiAlias()); + paint.setAntiAlias(antiAlias); + } + + if (a.hasValue(R.styleable.BitmapDrawable_filter)) { + final boolean filter = a.getBoolean( + R.styleable.BitmapDrawable_filter, paint.isFilterBitmap()); + paint.setFilterBitmap(filter); + } + + if (a.hasValue(R.styleable.BitmapDrawable_dither)) { + final boolean dither = a.getBoolean( + R.styleable.BitmapDrawable_dither, paint.isDither()); + paint.setDither(dither); + } + + if (a.hasValue(R.styleable.BitmapDrawable_gravity)) { + final int gravity = a.getInt( + R.styleable.BitmapDrawable_gravity, Gravity.FILL); + setGravity(gravity); + } + + if (a.hasValue(R.styleable.BitmapDrawable_tileMode)) { + final int tileMode = a.getInt( + R.styleable.BitmapDrawable_tileMode, -1); + setTileModeInternal(tileMode); + } + + if (a.hasValue(R.styleable.BitmapDrawable_src)) { + final int id = a.getResourceId(R.styleable.BitmapDrawable_src, 0); + if (id == 0) { + throw new RuntimeException(a.getPositionDescription() + + ": <bitmap> requires a valid src attribute"); + } + + final Bitmap bitmap = BitmapFactory.decodeResource(r, id); + if (bitmap == null) { + throw new RuntimeException(a.getPositionDescription() + + ": <bitmap> requires a valid src attribute"); + } + + setBitmap(bitmap); + } + setTargetDensity(r.getDisplayMetrics()); - setMipMap(a.getBoolean(com.android.internal.R.styleable.BitmapDrawable_mipMap, - bitmap.hasMipMap())); - setAutoMirrored(a.getBoolean(com.android.internal.R.styleable.BitmapDrawable_autoMirrored, - false)); - - final int tintModeValue = a.getInt( - com.android.internal.R.styleable.BitmapDrawable_tintMode, -1); - state.mTintMode = Drawable.parseTintMode(tintModeValue, Mode.SRC_IN); - state.mTint = a.getColorStateList(com.android.internal.R.styleable.BitmapDrawable_tint); - if (state.mTint != null) { - final int color = state.mTint.getColorForState(getState(), 0); - mTintFilter = new PorterDuffColorFilter(color, mBitmapState.mTintMode); + + if (a.hasValue(R.styleable.BitmapDrawable_mipMap)) { + final boolean mipMap = a.getBoolean( + R.styleable.BitmapDrawable_mipMap, + state.mBitmap.hasMipMap()); + setMipMap(mipMap); } - final Paint paint = state.mPaint; - paint.setAntiAlias(a.getBoolean(com.android.internal.R.styleable.BitmapDrawable_antialias, - paint.isAntiAlias())); - paint.setFilterBitmap(a.getBoolean(com.android.internal.R.styleable.BitmapDrawable_filter, - paint.isFilterBitmap())); - paint.setDither(a.getBoolean(com.android.internal.R.styleable.BitmapDrawable_dither, - paint.isDither())); - setGravity(a.getInt(com.android.internal.R.styleable.BitmapDrawable_gravity, Gravity.FILL)); - int tileMode = a.getInt(com.android.internal.R.styleable.BitmapDrawable_tileMode, -1); - if (tileMode != -1) { - switch (tileMode) { - case 0: - setTileModeXY(Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); - break; - case 1: - setTileModeXY(Shader.TileMode.REPEAT, Shader.TileMode.REPEAT); - break; - case 2: - setTileModeXY(Shader.TileMode.MIRROR, Shader.TileMode.MIRROR); - break; + if (a.hasValue(R.styleable.BitmapDrawable_autoMirrored)) { + final boolean autoMirrored = a.getBoolean( + R.styleable.BitmapDrawable_autoMirrored, false); + setAutoMirrored(autoMirrored); + } + + if (a.hasValue(R.styleable.BitmapDrawable_tintMode)) { + final int modeValue = a.getInt( + R.styleable.BitmapDrawable_tintMode, -1); + state.mTintMode = Drawable.parseTintMode(modeValue, Mode.SRC_IN); + } + + if (a.hasValue(R.styleable.BitmapDrawable_tint)) { + final ColorStateList tint = a.getColorStateList( + R.styleable.BitmapDrawable_tint); + if (tint != null) { + state.mTint = tint; + final int color = tint.getColorForState(getState(), 0); + mTintFilter = new PorterDuffColorFilter(color, state.mTintMode); } } + } - a.recycle(); + private void setTileModeInternal(final int tileMode) { + switch (tileMode) { + case -1: + // Do nothing. + break; + case 0: + setTileModeXY(Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); + break; + case 1: + setTileModeXY(Shader.TileMode.REPEAT, Shader.TileMode.REPEAT); + break; + case 2: + setTileModeXY(Shader.TileMode.MIRROR, Shader.TileMode.MIRROR); + break; + } + } + + @Override + public boolean canApplyTheme() { + return mBitmapState != null && mBitmapState.mThemeAttrs != null; } @Override @@ -766,6 +934,7 @@ public class BitmapDrawable extends Drawable { Bitmap mBitmap; ColorStateList mTint; Mode mTintMode = Mode.SRC_IN; + int[] mThemeAttrs; int mChangingConfigurations; int mGravity = Gravity.FILL; Paint mPaint = new Paint(DEFAULT_PAINT_FLAGS); @@ -783,6 +952,7 @@ public class BitmapDrawable extends Drawable { mBitmap = bitmapState.mBitmap; mTint = bitmapState.mTint; mTintMode = bitmapState.mTintMode; + mThemeAttrs = bitmapState.mThemeAttrs; mChangingConfigurations = bitmapState.mChangingConfigurations; mGravity = bitmapState.mGravity; mTileModeX = bitmapState.mTileModeX; @@ -794,18 +964,28 @@ public class BitmapDrawable extends Drawable { } @Override + public boolean canApplyTheme() { + return mThemeAttrs != null; + } + + @Override public Bitmap getBitmap() { return mBitmap; } @Override public Drawable newDrawable() { - return new BitmapDrawable(this, null); + return new BitmapDrawable(this, null, null); } @Override public Drawable newDrawable(Resources res) { - return new BitmapDrawable(this, res); + return new BitmapDrawable(this, res, null); + } + + @Override + public Drawable newDrawable(Resources res, Theme theme) { + return new BitmapDrawable(this, res, theme); } @Override @@ -814,9 +994,21 @@ public class BitmapDrawable extends Drawable { } } - private BitmapDrawable(BitmapState state, Resources res) { - mBitmapState = state; + private BitmapDrawable(BitmapState state, Resources res, Theme theme) { + if (theme != null && state.canApplyTheme()) { + mBitmapState = new BitmapState(state); + applyTheme(theme); + } else { + mBitmapState = state; + } + + initializeWithState(state, res); + } + /** + * Initializes local dynamic properties from state. + */ + private void initializeWithState(BitmapState state, Resources res) { if (res != null) { mTargetDensity = res.getDisplayMetrics().densityDpi; } else { @@ -828,6 +1020,6 @@ public class BitmapDrawable extends Drawable { mTintFilter = new PorterDuffColorFilter(color, state.mTintMode); } - setBitmap(state != null ? state.mBitmap : null); + setBitmap(state.mBitmap); } } diff --git a/graphics/java/android/graphics/drawable/ClipDrawable.java b/graphics/java/android/graphics/drawable/ClipDrawable.java index 2a9a14b..3dbd235 100644 --- a/graphics/java/android/graphics/drawable/ClipDrawable.java +++ b/graphics/java/android/graphics/drawable/ClipDrawable.java @@ -21,6 +21,7 @@ import org.xmlpull.v1.XmlPullParserException; import android.content.res.Resources; import android.content.res.TypedArray; +import android.content.res.Resources.Theme; import android.graphics.*; import android.view.Gravity; import android.util.AttributeSet; @@ -72,9 +73,9 @@ public class ClipDrawable extends Drawable implements Drawable.Callback { } @Override - public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs) + public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) throws XmlPullParserException, IOException { - super.inflate(r, parser, attrs); + super.inflate(r, parser, attrs, theme); int type; @@ -94,7 +95,7 @@ public class ClipDrawable extends Drawable implements Drawable.Callback { if (type != XmlPullParser.START_TAG) { continue; } - dr = Drawable.createFromXmlInner(r, parser, attrs); + dr = Drawable.createFromXmlInnerThemed(r, parser, attrs, theme); } if (dr == null) { diff --git a/graphics/java/android/graphics/drawable/ColorDrawable.java b/graphics/java/android/graphics/drawable/ColorDrawable.java index 61dd675..8243b7c 100644 --- a/graphics/java/android/graphics/drawable/ColorDrawable.java +++ b/graphics/java/android/graphics/drawable/ColorDrawable.java @@ -18,9 +18,13 @@ package android.graphics.drawable; import android.graphics.*; import android.content.res.Resources; +import android.content.res.Resources.Theme; import android.content.res.TypedArray; import android.util.AttributeSet; import android.view.ViewDebug; + +import com.android.internal.R; + import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -36,7 +40,7 @@ import java.io.IOException; */ public class ColorDrawable extends Drawable { @ViewDebug.ExportedProperty(deepExport = true, prefix = "state_") - private ColorState mState; + private ColorState mColorState; private final Paint mPaint = new Paint(); private boolean mMutated; @@ -44,7 +48,7 @@ public class ColorDrawable extends Drawable { * Creates a new black ColorDrawable. */ public ColorDrawable() { - this(null); + mColorState = new ColorState(); } /** @@ -53,17 +57,14 @@ public class ColorDrawable extends Drawable { * @param color The color to draw. */ public ColorDrawable(int color) { - this(null); - setColor(color); - } + mColorState = new ColorState(); - private ColorDrawable(ColorState state) { - mState = new ColorState(state); + setColor(color); } @Override public int getChangingConfigurations() { - return super.getChangingConfigurations() | mState.mChangingConfigurations; + return super.getChangingConfigurations() | mColorState.mChangingConfigurations; } /** @@ -75,7 +76,7 @@ public class ColorDrawable extends Drawable { @Override public Drawable mutate() { if (!mMutated && super.mutate() == this) { - mState = new ColorState(mState); + mColorState = new ColorState(mColorState); mMutated = true; } return this; @@ -83,8 +84,8 @@ public class ColorDrawable extends Drawable { @Override public void draw(Canvas canvas) { - if ((mState.mUseColor >>> 24) != 0) { - mPaint.setColor(mState.mUseColor); + if ((mColorState.mUseColor >>> 24) != 0) { + mPaint.setColor(mColorState.mUseColor); canvas.drawRect(getBounds(), mPaint); } } @@ -95,19 +96,20 @@ public class ColorDrawable extends Drawable { * @return int The color to draw. */ public int getColor() { - return mState.mUseColor; + return mColorState.mUseColor; } /** - * Sets the drawable's color value. This action will clobber the results of prior calls to - * {@link #setAlpha(int)} on this object, which side-affected the underlying color. + * Sets the drawable's color value. This action will clobber the results of + * prior calls to {@link #setAlpha(int)} on this object, which side-affected + * the underlying color. * * @param color The color to draw. */ public void setColor(int color) { - if (mState.mBaseColor != color || mState.mUseColor != color) { + if (mColorState.mBaseColor != color || mColorState.mUseColor != color) { + mColorState.mBaseColor = mColorState.mUseColor = color; invalidateSelf(); - mState.mBaseColor = mState.mUseColor = color; } } @@ -118,7 +120,7 @@ public class ColorDrawable extends Drawable { */ @Override public int getAlpha() { - return mState.mUseColor >>> 24; + return mColorState.mUseColor >>> 24; } /** @@ -126,13 +128,14 @@ public class ColorDrawable extends Drawable { * * @param alpha The alpha value to set, between 0 and 255. */ + @Override public void setAlpha(int alpha) { alpha += alpha >> 7; // make it 0..256 - int baseAlpha = mState.mBaseColor >>> 24; - int useAlpha = baseAlpha * alpha >> 8; - int oldUseColor = mState.mUseColor; - mState.mUseColor = (mState.mBaseColor << 8 >>> 8) | (useAlpha << 24); - if (oldUseColor != mState.mUseColor) { + final int baseAlpha = mColorState.mBaseColor >>> 24; + final int useAlpha = baseAlpha * alpha >> 8; + final int useColor = (mColorState.mBaseColor << 8 >>> 8) | (useAlpha << 24); + if (mColorState.mUseColor != useColor) { + mColorState.mUseColor = useColor; invalidateSelf(); } } @@ -142,11 +145,13 @@ public class ColorDrawable extends Drawable { * * @param colorFilter Ignore. */ + @Override public void setColorFilter(ColorFilter colorFilter) { } + @Override public int getOpacity() { - switch (mState.mUseColor >>> 24) { + switch (mColorState.mUseColor >>> 24) { case 255: return PixelFormat.OPAQUE; case 0: @@ -156,23 +161,67 @@ public class ColorDrawable extends Drawable { } @Override - public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs) + public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) throws XmlPullParserException, IOException { - super.inflate(r, parser, attrs); + super.inflate(r, parser, attrs, theme); + + final TypedArray a = obtainAttributes( + r, theme, attrs, R.styleable.ColorDrawable); + inflateStateFromTypedArray(a); + a.recycle(); + } - TypedArray a = r.obtainAttributes(attrs, com.android.internal.R.styleable.ColorDrawable); + /** + * Initializes the constant state from the values in the typed array. + */ + private void inflateStateFromTypedArray(TypedArray a) { + final ColorState state = mColorState; - int color = mState.mBaseColor; - color = a.getColor(com.android.internal.R.styleable.ColorDrawable_color, color); - mState.mBaseColor = mState.mUseColor = color; + // Extract the theme attributes, if any. + final int[] themeAttrs = a.extractThemeAttrs(); + state.mThemeAttrs = themeAttrs; - a.recycle(); + if (themeAttrs == null || themeAttrs[R.styleable.ColorDrawable_color] == 0) { + final int color = a.getColor(R.styleable.ColorDrawable_color, 0); + state.mBaseColor = color; + state.mUseColor = color; + } + } + + @Override + public void applyTheme(Theme t) { + super.applyTheme(t); + + final ColorState state = mColorState; + if (state == null) { + throw new RuntimeException("Can't apply theme to <color> with no constant state"); + } + + final int[] themeAttrs = state.mThemeAttrs; + if (themeAttrs != null) { + final TypedArray a = t.resolveAttributes(themeAttrs, R.styleable.ColorDrawable, 0, 0); + updateStateFromTypedArray(a); + a.recycle(); + } + } + + /** + * Updates the constant state from the values in the typed array. + */ + private void updateStateFromTypedArray(TypedArray a) { + final ColorState state = mColorState; + + if (a.hasValue(R.styleable.ColorDrawable_color)) { + final int color = a.getColor(R.styleable.ColorDrawable_color, 0); + state.mBaseColor = color; + state.mUseColor = color; + } } @Override public ConstantState getConstantState() { - mState.mChangingConfigurations = getChangingConfigurations(); - return mState; + mColorState.mChangingConfigurations = getChangingConfigurations(); + return mColorState; } final static class ColorState extends ConstantState { @@ -180,23 +229,37 @@ public class ColorDrawable extends Drawable { @ViewDebug.ExportedProperty int mUseColor; // basecolor modulated by setAlpha() int mChangingConfigurations; + int[] mThemeAttrs; + + ColorState() { + // Empty constructor. + } ColorState(ColorState state) { - if (state != null) { - mBaseColor = state.mBaseColor; - mUseColor = state.mUseColor; - mChangingConfigurations = state.mChangingConfigurations; - } + mBaseColor = state.mBaseColor; + mUseColor = state.mUseColor; + mChangingConfigurations = state.mChangingConfigurations; + mThemeAttrs = state.mThemeAttrs; + } + + @Override + public boolean canApplyTheme() { + return mThemeAttrs != null; } @Override public Drawable newDrawable() { - return new ColorDrawable(this); + return new ColorDrawable(this, null, null); } @Override public Drawable newDrawable(Resources res) { - return new ColorDrawable(this); + return new ColorDrawable(this, res, null); + } + + @Override + public Drawable newDrawable(Resources res, Theme theme) { + return new ColorDrawable(this, res, theme); } @Override @@ -204,4 +267,15 @@ public class ColorDrawable extends Drawable { return mChangingConfigurations; } } + + private ColorDrawable(ColorState state, Resources res, Theme theme) { + if (theme != null && state.canApplyTheme()) { + mColorState = new ColorState(state); + applyTheme(theme); + } else { + mColorState = state; + } + + // No local properties to initialize. + } } diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java index eb6b536..de2b68f 100644 --- a/graphics/java/android/graphics/drawable/Drawable.java +++ b/graphics/java/android/graphics/drawable/Drawable.java @@ -24,6 +24,7 @@ import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import android.content.res.Resources; +import android.content.res.Resources.Theme; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.BitmapFactory; @@ -697,6 +698,16 @@ public abstract class Drawable { } /** + * Applies the specified theme to this Drawable and its children. + */ + public void applyTheme(@SuppressWarnings("unused") Theme t) { + } + + public boolean canApplyTheme() { + return false; + } + + /** * Return the opacity/transparency of this Drawable. The returned value is * one of the abstract format constants in * {@link android.graphics.PixelFormat}: @@ -900,9 +911,13 @@ public abstract class Drawable { * Create a drawable from an inputstream */ public static Drawable createFromStream(InputStream is, String srcName) { + return createFromStreamThemed(is, srcName, null); + } + + public static Drawable createFromStreamThemed(InputStream is, String srcName, Theme theme) { Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, srcName != null ? srcName : "Unknown drawable"); try { - return createFromResourceStream(null, null, is, srcName, null); + return createFromResourceStreamThemed(null, null, is, srcName, theme); } finally { Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); } @@ -914,9 +929,14 @@ public abstract class Drawable { */ public static Drawable createFromResourceStream(Resources res, TypedValue value, InputStream is, String srcName) { + return createFromResourceStreamThemed(res, value, is, srcName, null); + } + + public static Drawable createFromResourceStreamThemed(Resources res, TypedValue value, + InputStream is, String srcName, Theme theme) { Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, srcName != null ? srcName : "Unknown drawable"); try { - return createFromResourceStream(res, value, is, srcName, null); + return createFromResourceStreamThemed(res, value, is, srcName, null, theme); } finally { Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); } @@ -928,7 +948,11 @@ public abstract class Drawable { */ public static Drawable createFromResourceStream(Resources res, TypedValue value, InputStream is, String srcName, BitmapFactory.Options opts) { + return createFromResourceStreamThemed(res, value, is, srcName, opts, null); + } + public static Drawable createFromResourceStreamThemed(Resources res, TypedValue value, + InputStream is, String srcName, BitmapFactory.Options opts, Theme theme) { if (is == null) { return null; } @@ -976,6 +1000,16 @@ public abstract class Drawable { */ public static Drawable createFromXml(Resources r, XmlPullParser parser) throws XmlPullParserException, IOException { + return createFromXmlThemed(r, parser, null); + } + + /** + * Create a themed drawable from an XML document. For more information on + * how to create resources in XML, see + * <a href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable Resources</a>. + */ + public static Drawable createFromXmlThemed(Resources r, XmlPullParser parser, Theme theme) + throws XmlPullParserException, IOException { AttributeSet attrs = Xml.asAttributeSet(parser); int type; @@ -988,7 +1022,7 @@ public abstract class Drawable { throw new XmlPullParserException("No start tag found"); } - Drawable drawable = createFromXmlInner(r, parser, attrs); + Drawable drawable = createFromXmlInnerThemed(r, parser, attrs, theme); if (drawable == null) { throw new RuntimeException("Unknown initial tag: " + parser.getName()); @@ -1003,11 +1037,15 @@ public abstract class Drawable { * Returns null if the tag is not a valid drawable. */ public static Drawable createFromXmlInner(Resources r, XmlPullParser parser, AttributeSet attrs) - throws XmlPullParserException, IOException { - Drawable drawable; + throws XmlPullParserException, IOException { + return createFromXmlInnerThemed(r, parser, attrs, null); + } - final String name = parser.getName(); + public static Drawable createFromXmlInnerThemed(Resources r, XmlPullParser parser, + AttributeSet attrs, Theme theme) throws XmlPullParserException, IOException { + final Drawable drawable; + final String name = parser.getName(); if (name.equals("selector")) { drawable = new StateListDrawable(); } else if (name.equals("level-list")) { @@ -1052,7 +1090,7 @@ public abstract class Drawable { ": invalid drawable tag " + name); } - drawable.inflate(r, parser, attrs); + drawable.inflate(r, parser, attrs, theme); return drawable; } @@ -1079,12 +1117,35 @@ public abstract class Drawable { } /** - * Inflate this Drawable from an XML resource. + * Inflate this Drawable from an XML resource. Does not apply a theme. + * + * @see #inflate(Resources, XmlPullParser, AttributeSet, Theme) */ public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs) throws XmlPullParserException, IOException { + inflate(r, parser, attrs, null); + } + + /** + * Inflate this Drawable from an XML resource optionally styled by a theme. + * + * @param r Resources used to resolve attribute values + * @param parser XML parser from which to inflate this Drawable + * @param attrs Base set of attribute values + * @param theme Theme to apply, may be null + * @throws XmlPullParserException + * @throws IOException + */ + public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) + throws XmlPullParserException, IOException { + final TypedArray a; + if (theme != null) { + a = theme.obtainStyledAttributes( + attrs, com.android.internal.R.styleable.Drawable, 0, 0); + } else { + a = r.obtainAttributes(attrs, com.android.internal.R.styleable.Drawable); + } - TypedArray a = r.obtainAttributes(attrs, com.android.internal.R.styleable.Drawable); inflateWithAttributes(r, parser, a, com.android.internal.R.styleable.Drawable_visible); a.recycle(); } @@ -1095,10 +1156,8 @@ public abstract class Drawable { * @throws XmlPullParserException * @throws IOException */ - void inflateWithAttributes(Resources r, XmlPullParser parser, - TypedArray attrs, int visibleAttr) + void inflateWithAttributes(Resources r, XmlPullParser parser, TypedArray attrs, int visibleAttr) throws XmlPullParserException, IOException { - mVisible = attrs.getBoolean(visibleAttr, mVisible); } @@ -1125,6 +1184,7 @@ public abstract class Drawable { * instead to provide a resource. */ public abstract Drawable newDrawable(); + /** * Create a new Drawable instance from its constant state. This * must be implemented for drawables that change based on the target @@ -1134,6 +1194,15 @@ public abstract class Drawable { public Drawable newDrawable(Resources res) { return newDrawable(); } + + /** + * Create a new Drawable instance from its constant state. This must be + * implemented for drawables that can have a theme applied. + */ + public Drawable newDrawable(Resources res, Theme theme) { + return newDrawable(); + } + /** * Return a bit mask of configuration changes that will impact * this drawable (and thus require completely reloading it). @@ -1146,6 +1215,13 @@ public abstract class Drawable { public Bitmap getBitmap() { return null; } + + /** + * Return whether this constant state can have a theme applied. + */ + public boolean canApplyTheme() { + return false; + } } /** @@ -1170,6 +1246,18 @@ public abstract class Drawable { } /** + * 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); + } + + /** * Parses a {@link android.graphics.PorterDuff.Mode} from a tintMode * attribute's enum value. */ diff --git a/graphics/java/android/graphics/drawable/DrawableContainer.java b/graphics/java/android/graphics/drawable/DrawableContainer.java index 6144e69..05df3bc 100644 --- a/graphics/java/android/graphics/drawable/DrawableContainer.java +++ b/graphics/java/android/graphics/drawable/DrawableContainer.java @@ -17,6 +17,7 @@ package android.graphics.drawable; import android.content.res.Resources; +import android.content.res.Resources.Theme; import android.graphics.Canvas; import android.graphics.ColorFilter; import android.graphics.Insets; @@ -456,6 +457,16 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { } @Override + public void applyTheme(Theme theme) { + mDrawableContainerState.applyTheme(theme); + } + + @Override + public boolean canApplyTheme() { + return mDrawableContainerState.canApplyTheme(); + } + + @Override public ConstantState getConstantState() { if (mDrawableContainerState.canConstantState()) { mDrawableContainerState.mChangingConfigurations = getChangingConfigurations(); @@ -483,6 +494,8 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { final DrawableContainer mOwner; final Resources mRes; + Theme mTheme; + SparseArray<ConstantStateFuture> mDrawableFutures; int mChangingConfigurations; @@ -680,6 +693,41 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { mLayoutDirection = layoutDirection; } + final void applyTheme(Theme theme) { + // No need to call createAllFutures, since future drawables will + // apply the theme when they are prepared. + final int N = mNumChildren; + final Drawable[] drawables = mDrawables; + for (int i = 0; i < N; i++) { + if (drawables[i] != null) { + drawables[i].applyTheme(theme); + } + } + + mTheme = theme; + } + + @Override + public boolean canApplyTheme() { + final int N = mNumChildren; + final Drawable[] drawables = mDrawables; + for (int i = 0; i < N; i++) { + final Drawable d = drawables[i]; + if (d != null) { + if (d.canApplyTheme()) { + return true; + } + } else { + final ConstantStateFuture future = mDrawableFutures.get(i); + if (future != null && future.canApplyTheme()) { + return true; + } + } + } + + return false; + } + final void mutate() { // No need to call createAllFutures, since future drawables will // mutate when they are prepared. @@ -898,8 +946,14 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { * @return a prepared Drawable */ public Drawable get(DrawableContainerState state) { - final Drawable result = (state.mRes == null) ? - mConstantState.newDrawable() : mConstantState.newDrawable(state.mRes); + final Drawable result; + if (state.mRes == null) { + result = mConstantState.newDrawable(); + } else if (state.mTheme == null) { + result = mConstantState.newDrawable(state.mRes); + } else { + result = mConstantState.newDrawable(state.mRes, state.mTheme); + } result.setLayoutDirection(state.mLayoutDirection); result.setCallback(state.mOwner); @@ -909,6 +963,14 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { return result; } + + /** + * Whether the constant state wrapped by this future can apply a + * theme. + */ + public boolean canApplyTheme() { + return mConstantState.canApplyTheme(); + } } } diff --git a/graphics/java/android/graphics/drawable/GradientDrawable.java b/graphics/java/android/graphics/drawable/GradientDrawable.java index 8f22add..1568e99 100644 --- a/graphics/java/android/graphics/drawable/GradientDrawable.java +++ b/graphics/java/android/graphics/drawable/GradientDrawable.java @@ -19,6 +19,7 @@ package android.graphics.drawable; import android.content.res.ColorStateList; import android.content.res.Resources; import android.content.res.TypedArray; +import android.content.res.Resources.Theme; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.ColorFilter; @@ -33,11 +34,12 @@ import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Shader; import android.graphics.SweepGradient; -import android.os.Build; import android.util.AttributeSet; import android.util.Log; import android.util.TypedValue; +import com.android.internal.R; + import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -123,6 +125,9 @@ public class GradientDrawable extends Drawable { /** Radius is a fraction of the bounds size. */ private static final int RADIUS_TYPE_FRACTION_PARENT = 2; + private static final float DEFAULT_INNER_RADIUS_RATIO = 3.0f; + private static final float DEFAULT_THICKNESS_RATIO = 9.0f; + private GradientState mGradientState; private final Paint mFillPaint = new Paint(Paint.ANTI_ALIAS_FLAG); @@ -168,7 +173,7 @@ public class GradientDrawable extends Drawable { } public GradientDrawable() { - this(new GradientState(Orientation.TOP_BOTTOM, null)); + this(new GradientState(Orientation.TOP_BOTTOM, null), null); } /** @@ -176,7 +181,7 @@ public class GradientDrawable extends Drawable { * of colors for the gradient. */ public GradientDrawable(Orientation orientation, int[] colors) { - this(new GradientState(orientation, colors)); + this(new GradientState(orientation, colors), null); } @Override @@ -857,7 +862,7 @@ public class GradientDrawable extends Drawable { float x0, x1, y0, y1; if (st.mGradient == LINEAR_GRADIENT) { - final float level = st.mUseLevel ? (float) getLevel() / 10000.0f : 1.0f; + final float level = st.mUseLevel ? getLevel() / 10000.0f : 1.0f; switch (st.mOrientation) { case TOP_BOTTOM: x0 = r.left; y0 = r.top; @@ -937,12 +942,12 @@ public class GradientDrawable extends Drawable { tempColors[length] = colors[length - 1]; tempPositions = st.mTempPositions; - final float fraction = 1.0f / (float) (length - 1); + final float fraction = 1.0f / (length - 1); if (tempPositions == null || tempPositions.length != length + 1) { tempPositions = st.mTempPositions = new float[length + 1]; } - final float level = (float) getLevel() / 10000.0f; + final float level = getLevel() / 10000.0f; for (int i = 0; i < length; i++) { tempPositions[i] = i * fraction * level; } @@ -963,45 +968,196 @@ public class GradientDrawable extends Drawable { } @Override - public void inflate(Resources r, XmlPullParser parser, - AttributeSet attrs) + public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) throws XmlPullParserException, IOException { - - final GradientState st = mGradientState; - - TypedArray a = r.obtainAttributes(attrs, - com.android.internal.R.styleable.GradientDrawable); + final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.GradientDrawable); + super.inflateWithAttributes(r, parser, a, R.styleable.GradientDrawable_visible); - super.inflateWithAttributes(r, parser, a, - com.android.internal.R.styleable.GradientDrawable_visible); - - int shapeType = a.getInt( - com.android.internal.R.styleable.GradientDrawable_shape, RECTANGLE); - boolean dither = a.getBoolean( - com.android.internal.R.styleable.GradientDrawable_dither, false); - - if (shapeType == RING) { - st.mInnerRadius = a.getDimensionPixelSize( - com.android.internal.R.styleable.GradientDrawable_innerRadius, -1); - if (st.mInnerRadius == -1) { - st.mInnerRadiusRatio = a.getFloat( - com.android.internal.R.styleable.GradientDrawable_innerRadiusRatio, 3.0f); + inflateStateFromTypedArray(a); + a.recycle(); + + inflateChildElements(r, parser, attrs, theme); + + mGradientState.computeOpacity(); + } + + /** + * Initializes the constant state from the values in the typed array. + */ + private void inflateStateFromTypedArray(TypedArray a) { + final GradientState state = mGradientState; + + // Extract the theme attributes, if any. + final int[] themeAttrs = a.extractThemeAttrs(); + state.mThemeAttrs = themeAttrs; + + final boolean needsRingAttrs; + if (themeAttrs == null || themeAttrs[R.styleable.GradientDrawable_shape] == 0) { + final int shapeType = a.getInt(R.styleable.GradientDrawable_shape, RECTANGLE); + setShape(shapeType); + needsRingAttrs = shapeType == RING; + } else { + needsRingAttrs = true; + } + + // We only need to load ring attributes if the shape type is a theme + // attribute (e.g. unknown) or defined in XML as RING. + if (needsRingAttrs) { + if (themeAttrs == null || themeAttrs[R.styleable.GradientDrawable_innerRadius] == 0) { + state.mInnerRadius = a.getDimensionPixelSize( + R.styleable.GradientDrawable_innerRadius, -1); } - st.mThickness = a.getDimensionPixelSize( - com.android.internal.R.styleable.GradientDrawable_thickness, -1); - if (st.mThickness == -1) { - st.mThicknessRatio = a.getFloat( - com.android.internal.R.styleable.GradientDrawable_thicknessRatio, 9.0f); + + if (state.mInnerRadius == -1 + && themeAttrs == null || themeAttrs[R.styleable.GradientDrawable_thicknessRatio] == 0) { + state.mInnerRadiusRatio = a.getFloat( + R.styleable.GradientDrawable_innerRadiusRatio, DEFAULT_INNER_RADIUS_RATIO); + } + + if (themeAttrs == null || themeAttrs[R.styleable.GradientDrawable_thickness] == 0) { + state.mThickness = a.getDimensionPixelSize( + R.styleable.GradientDrawable_thickness, -1); + } + + if (state.mThickness == -1 + && themeAttrs == null || themeAttrs[R.styleable.GradientDrawable_thicknessRatio] == 0) { + state.mThicknessRatio = a.getFloat( + R.styleable.GradientDrawable_thicknessRatio, DEFAULT_THICKNESS_RATIO); + } + + if (themeAttrs == null || themeAttrs[R.styleable.GradientDrawable_useLevel] == 0) { + state.mUseLevelForShape = a.getBoolean( + R.styleable.GradientDrawable_useLevel, true); } - st.mUseLevelForShape = a.getBoolean( - com.android.internal.R.styleable.GradientDrawable_useLevel, true); } - - a.recycle(); - - setShape(shapeType); - setDither(dither); + if (themeAttrs == null || themeAttrs[R.styleable.GradientDrawable_dither] == 0) { + final boolean dither = a.getBoolean(R.styleable.GradientDrawable_dither, false); + setDither(dither); + } + } + + @Override + public void applyTheme(Theme t) { + super.applyTheme(t); + + final GradientState state = mGradientState; + if (state == null) { + throw new RuntimeException("Can't apply theme to <shape> with no constant state"); + } + + final int[] themeAttrs = state.mThemeAttrs; + if (themeAttrs != null) { + final TypedArray a = t.resolveAttributes( + themeAttrs, R.styleable.GradientDrawable, 0, 0); + updateStateFromTypedArray(a); + a.recycle(); + + applyThemeChildElements(t); + + mGradientState.computeOpacity(); + } + } + + /** + * Updates the constant state from the values in the typed array. + */ + private void updateStateFromTypedArray(TypedArray a) { + final GradientState state = mGradientState; + + if (a.hasValue(R.styleable.GradientDrawable_shape)) { + final int shapeType = a.getInt(R.styleable.GradientDrawable_shape, RECTANGLE); + setShape(shapeType); + } + + if (a.hasValue(R.styleable.GradientDrawable_dither)) { + final boolean dither = a.getBoolean(R.styleable.GradientDrawable_dither, false); + setDither(dither); + } + + if (state.mShape == RING) { + if (a.hasValue(R.styleable.GradientDrawable_innerRadius)) { + state.mInnerRadius = a.getDimensionPixelSize( + R.styleable.GradientDrawable_innerRadius, -1); + } + + if (state.mInnerRadius == -1 && a.hasValue( + R.styleable.GradientDrawable_innerRadiusRatio)) { + state.mInnerRadiusRatio = a.getFloat( + R.styleable.GradientDrawable_innerRadiusRatio, DEFAULT_INNER_RADIUS_RATIO); + } + + if (a.hasValue(R.styleable.GradientDrawable_thickness)) { + state.mThickness = a.getDimensionPixelSize( + R.styleable.GradientDrawable_thickness, -1); + } + + if (state.mThickness == -1 && a.hasValue( + R.styleable.GradientDrawable_thicknessRatio)) { + state.mThicknessRatio = a.getFloat( + R.styleable.GradientDrawable_thicknessRatio, DEFAULT_THICKNESS_RATIO); + } + + if (a.hasValue(R.styleable.GradientDrawable_useLevel)) { + state.mUseLevelForShape = a.getBoolean( + R.styleable.GradientDrawable_useLevel, true); + } + } + } + + @Override + public boolean canApplyTheme() { + final GradientState state = mGradientState; + return state != null && (state.mThemeAttrs != null || state.mAttrSize != null + || state.mAttrGradient != null || state.mAttrSolid != null + || state.mAttrStroke != null || state.mAttrCorners != null + || state.mAttrPadding != null); + } + + private void applyThemeChildElements(Theme t) { + final GradientState state = mGradientState; + TypedArray a; + + if (state.mAttrSize != null) { + a = t.resolveAttributes(state.mAttrSize, R.styleable.GradientDrawableSize, 0, 0); + // TODO: updateGradientDrawableSize(a); + a.recycle(); + } + + if (state.mAttrGradient != null) { + a = t.resolveAttributes(state.mAttrGradient, R.styleable.GradientDrawableGradient, 0, 0); + // TODO: updateGradientDrawableGradient(a); + a.recycle(); + } + + if (state.mAttrSolid != null) { + a = t.resolveAttributes(state.mAttrSolid, R.styleable.GradientDrawableSolid, 0, 0); + // TODO: updateGradientDrawableSolid(a); + a.recycle(); + } + + if (state.mAttrStroke != null) { + a = t.resolveAttributes(state.mAttrStroke, R.styleable.GradientDrawableStroke, 0, 0); + // TODO: updateGradientDrawableStroke(a); + a.recycle(); + } + + if (state.mAttrCorners != null) { + a = t.resolveAttributes(state.mAttrCorners, R.styleable.DrawableCorners, 0, 0); + // TODO: updateDrawableCorners(a); + a.recycle(); + } + + if (state.mAttrPadding != null) { + a = t.resolveAttributes(state.mAttrPadding, R.styleable.GradientDrawablePadding, 0, 0); + // TODO: updateGradientDrawablePadding(a); + a.recycle(); + } + } + + private void inflateChildElements(Resources r, XmlPullParser parser, AttributeSet attrs, + Theme theme) throws XmlPullParserException, IOException { + TypedArray a; int type; final int innerDepth = parser.getDepth() + 1; @@ -1016,203 +1172,233 @@ public class GradientDrawable extends Drawable { if (depth > innerDepth) { continue; } - + String name = parser.getName(); if (name.equals("size")) { - a = r.obtainAttributes(attrs, - com.android.internal.R.styleable.GradientDrawableSize); - int width = a.getDimensionPixelSize( - com.android.internal.R.styleable.GradientDrawableSize_width, -1); - int height = a.getDimensionPixelSize( - com.android.internal.R.styleable.GradientDrawableSize_height, -1); + a = obtainAttributes( + r, theme, attrs, R.styleable.GradientDrawableSize); + applyGradientDrawableSize(a); a.recycle(); - setSize(width, height); } else if (name.equals("gradient")) { - a = r.obtainAttributes(attrs, - com.android.internal.R.styleable.GradientDrawableGradient); - int startColor = a.getColor( - com.android.internal.R.styleable.GradientDrawableGradient_startColor, 0); - boolean hasCenterColor = a - .hasValue(com.android.internal.R.styleable.GradientDrawableGradient_centerColor); - int centerColor = a.getColor( - com.android.internal.R.styleable.GradientDrawableGradient_centerColor, 0); - int endColor = a.getColor( - com.android.internal.R.styleable.GradientDrawableGradient_endColor, 0); - int gradientType = a.getInt( - com.android.internal.R.styleable.GradientDrawableGradient_type, - LINEAR_GRADIENT); - - st.mCenterX = getFloatOrFraction( - a, - com.android.internal.R.styleable.GradientDrawableGradient_centerX, - 0.5f); - - st.mCenterY = getFloatOrFraction( - a, - com.android.internal.R.styleable.GradientDrawableGradient_centerY, - 0.5f); - - st.mUseLevel = a.getBoolean( - com.android.internal.R.styleable.GradientDrawableGradient_useLevel, false); - st.mGradient = gradientType; - - if (gradientType == LINEAR_GRADIENT) { - int angle = (int)a.getFloat( - com.android.internal.R.styleable.GradientDrawableGradient_angle, 0); - angle %= 360; - if (angle % 45 != 0) { - throw new XmlPullParserException(a.getPositionDescription() - + "<gradient> tag requires 'angle' attribute to " - + "be a multiple of 45"); - } - - switch (angle) { - case 0: - st.mOrientation = Orientation.LEFT_RIGHT; - break; - case 45: - st.mOrientation = Orientation.BL_TR; - break; - case 90: - st.mOrientation = Orientation.BOTTOM_TOP; - break; - case 135: - st.mOrientation = Orientation.BR_TL; - break; - case 180: - st.mOrientation = Orientation.RIGHT_LEFT; - break; - case 225: - st.mOrientation = Orientation.TR_BL; - break; - case 270: - st.mOrientation = Orientation.TOP_BOTTOM; - break; - case 315: - st.mOrientation = Orientation.TL_BR; - break; - } - } else { - final TypedValue tv = a.peekValue( - com.android.internal.R.styleable.GradientDrawableGradient_gradientRadius); - if (tv != null) { - final float radius; - final int radiusType; - if (tv.type == TypedValue.TYPE_FRACTION) { - radius = tv.getFraction(1.0f, 1.0f); - - final int unit = (tv.data >> TypedValue.COMPLEX_UNIT_SHIFT) - & TypedValue.COMPLEX_UNIT_MASK; - if (unit == TypedValue.COMPLEX_UNIT_FRACTION_PARENT) { - radiusType = RADIUS_TYPE_FRACTION_PARENT; - } else { - radiusType = RADIUS_TYPE_FRACTION; - } - } else { - radius = tv.getDimension(r.getDisplayMetrics()); - radiusType = RADIUS_TYPE_PIXELS; - } - - st.mGradientRadius = radius; - st.mGradientRadiusType = radiusType; - } else if (gradientType == RADIAL_GRADIENT) { - throw new XmlPullParserException( - a.getPositionDescription() - + "<gradient> tag requires 'gradientRadius' " - + "attribute with radial type"); - } - } - + a = obtainAttributes( + r, theme, attrs, R.styleable.GradientDrawableGradient); + applyGradientDrawableGradient(r, a); a.recycle(); - - if (hasCenterColor) { - st.mColors = new int[3]; - st.mColors[0] = startColor; - st.mColors[1] = centerColor; - st.mColors[2] = endColor; - - st.mPositions = new float[3]; - st.mPositions[0] = 0.0f; - // Since 0.5f is default value, try to take the one that isn't 0.5f - st.mPositions[1] = st.mCenterX != 0.5f ? st.mCenterX : st.mCenterY; - st.mPositions[2] = 1f; - } else { - st.mColors = new int[2]; - st.mColors[0] = startColor; - st.mColors[1] = endColor; - } - } else if (name.equals("solid")) { - a = r.obtainAttributes(attrs, - com.android.internal.R.styleable.GradientDrawableSolid); - final ColorStateList colorStateList = a.getColorStateList( - com.android.internal.R.styleable.GradientDrawableSolid_color); + a = obtainAttributes( + r, theme, attrs, R.styleable.GradientDrawableSolid); + applyGradientDrawableSolid(a); a.recycle(); - setColor(colorStateList); } else if (name.equals("stroke")) { - a = r.obtainAttributes(attrs, - com.android.internal.R.styleable.GradientDrawableStroke); - final int width = a.getDimensionPixelSize( - com.android.internal.R.styleable.GradientDrawableStroke_width, 0); - final ColorStateList colorStateList = a.getColorStateList( - com.android.internal.R.styleable.GradientDrawableStroke_color); - final float dashWidth = a.getDimension( - com.android.internal.R.styleable.GradientDrawableStroke_dashWidth, 0); - if (dashWidth != 0.0f) { - final float dashGap = a.getDimension( - com.android.internal.R.styleable.GradientDrawableStroke_dashGap, 0); - setStroke(width, colorStateList, dashWidth, dashGap); - } else { - setStroke(width, colorStateList); - } + a = obtainAttributes( + r, theme, attrs, R.styleable.GradientDrawableStroke); + applyGradientDrawableStroke(a); a.recycle(); } else if (name.equals("corners")) { - a = r.obtainAttributes(attrs, - com.android.internal.R.styleable.DrawableCorners); - int radius = a.getDimensionPixelSize( - com.android.internal.R.styleable.DrawableCorners_radius, 0); - setCornerRadius(radius); - int topLeftRadius = a.getDimensionPixelSize( - com.android.internal.R.styleable.DrawableCorners_topLeftRadius, radius); - int topRightRadius = a.getDimensionPixelSize( - com.android.internal.R.styleable.DrawableCorners_topRightRadius, radius); - int bottomLeftRadius = a.getDimensionPixelSize( - com.android.internal.R.styleable.DrawableCorners_bottomLeftRadius, radius); - int bottomRightRadius = a.getDimensionPixelSize( - com.android.internal.R.styleable.DrawableCorners_bottomRightRadius, radius); - if (topLeftRadius != radius || topRightRadius != radius || - bottomLeftRadius != radius || bottomRightRadius != radius) { - // The corner radii are specified in clockwise order (see Path.addRoundRect()) - setCornerRadii(new float[] { - topLeftRadius, topLeftRadius, - topRightRadius, topRightRadius, - bottomRightRadius, bottomRightRadius, - bottomLeftRadius, bottomLeftRadius - }); - } + a = obtainAttributes(r + , theme, attrs, R.styleable.DrawableCorners); + applyDrawableCorners(a); a.recycle(); } else if (name.equals("padding")) { - a = r.obtainAttributes(attrs, - com.android.internal.R.styleable.GradientDrawablePadding); - mPadding = new Rect( - a.getDimensionPixelOffset( - com.android.internal.R.styleable.GradientDrawablePadding_left, 0), - a.getDimensionPixelOffset( - com.android.internal.R.styleable.GradientDrawablePadding_top, 0), - a.getDimensionPixelOffset( - com.android.internal.R.styleable.GradientDrawablePadding_right, 0), - a.getDimensionPixelOffset( - com.android.internal.R.styleable.GradientDrawablePadding_bottom, 0)); + a = obtainAttributes( + r, theme, attrs, R.styleable.GradientDrawablePadding); + applyGradientDrawablePadding(a); a.recycle(); - mGradientState.mPadding = mPadding; } else { Log.w("drawable", "Bad element under <shape>: " + name); } + } + } + + private void applyGradientDrawablePadding(TypedArray a) { + mPadding = new Rect( + a.getDimensionPixelOffset( + R.styleable.GradientDrawablePadding_left, 0), + a.getDimensionPixelOffset( + R.styleable.GradientDrawablePadding_top, 0), + a.getDimensionPixelOffset( + R.styleable.GradientDrawablePadding_right, 0), + a.getDimensionPixelOffset( + R.styleable.GradientDrawablePadding_bottom, 0)); + mGradientState.mPadding = mPadding; + + // Extract the theme attributes, if any. + mGradientState.mAttrPadding = a.extractThemeAttrs(); + } + private void applyDrawableCorners(TypedArray a) { + int radius = a.getDimensionPixelSize( + R.styleable.DrawableCorners_radius, 0); + setCornerRadius(radius); + int topLeftRadius = a.getDimensionPixelSize( + R.styleable.DrawableCorners_topLeftRadius, radius); + int topRightRadius = a.getDimensionPixelSize( + R.styleable.DrawableCorners_topRightRadius, radius); + int bottomLeftRadius = a.getDimensionPixelSize( + R.styleable.DrawableCorners_bottomLeftRadius, radius); + int bottomRightRadius = a.getDimensionPixelSize( + R.styleable.DrawableCorners_bottomRightRadius, radius); + if (topLeftRadius != radius || topRightRadius != radius || + bottomLeftRadius != radius || bottomRightRadius != radius) { + // The corner radii are specified in clockwise order (see Path.addRoundRect()) + setCornerRadii(new float[] { + topLeftRadius, topLeftRadius, + topRightRadius, topRightRadius, + bottomRightRadius, bottomRightRadius, + bottomLeftRadius, bottomLeftRadius + }); } - mGradientState.computeOpacity(); + // Extract the theme attributes, if any. + mGradientState.mAttrCorners = a.extractThemeAttrs(); + } + + private void applyGradientDrawableStroke(TypedArray a) { + final int width = a.getDimensionPixelSize( + R.styleable.GradientDrawableStroke_width, 0); + final ColorStateList colorStateList = a.getColorStateList( + R.styleable.GradientDrawableStroke_color); + final float dashWidth = a.getDimension( + R.styleable.GradientDrawableStroke_dashWidth, 0); + if (dashWidth != 0.0f) { + final float dashGap = a.getDimension( + R.styleable.GradientDrawableStroke_dashGap, 0); + setStroke(width, colorStateList, dashWidth, dashGap); + } else { + setStroke(width, colorStateList); + } + + // Extract the theme attributes, if any. + mGradientState.mAttrStroke = a.extractThemeAttrs(); + } + + private void applyGradientDrawableSolid(TypedArray a) { + final ColorStateList colorStateList = a.getColorStateList( + R.styleable.GradientDrawableSolid_color); + setColor(colorStateList); + + // Extract the theme attributes, if any. + mGradientState.mAttrSolid = a.extractThemeAttrs(); + } + + private void applyGradientDrawableGradient(Resources r, TypedArray a) + throws XmlPullParserException { + final GradientState st = mGradientState; + final int startColor = a.getColor( + R.styleable.GradientDrawableGradient_startColor, 0); + final boolean hasCenterColor = a.hasValue( + R.styleable.GradientDrawableGradient_centerColor); + final int centerColor = a.getColor( + R.styleable.GradientDrawableGradient_centerColor, 0); + final int endColor = a.getColor( + R.styleable.GradientDrawableGradient_endColor, 0); + + if (hasCenterColor) { + st.mColors = new int[3]; + st.mColors[0] = startColor; + st.mColors[1] = centerColor; + st.mColors[2] = endColor; + + st.mPositions = new float[3]; + st.mPositions[0] = 0.0f; + // Since 0.5f is default value, try to take the one that isn't 0.5f + st.mPositions[1] = st.mCenterX != 0.5f ? st.mCenterX : st.mCenterY; + st.mPositions[2] = 1f; + } else { + st.mColors = new int[2]; + st.mColors[0] = startColor; + st.mColors[1] = endColor; + } + + st.mCenterX = getFloatOrFraction( + a, R.styleable.GradientDrawableGradient_centerX, 0.5f); + st.mCenterY = getFloatOrFraction( + a, R.styleable.GradientDrawableGradient_centerY, 0.5f); + st.mUseLevel = a.getBoolean( + R.styleable.GradientDrawableGradient_useLevel, false); + st.mGradient = a.getInt( + R.styleable.GradientDrawableGradient_type, LINEAR_GRADIENT); + + if (st.mGradient == LINEAR_GRADIENT) { + int angle = (int) a.getFloat( + R.styleable.GradientDrawableGradient_angle, 0); + angle %= 360; + + if (angle % 45 != 0) { + throw new XmlPullParserException(a.getPositionDescription() + + "<gradient> tag requires 'angle' attribute to " + + "be a multiple of 45"); + } + + switch (angle) { + case 0: + st.mOrientation = Orientation.LEFT_RIGHT; + break; + case 45: + st.mOrientation = Orientation.BL_TR; + break; + case 90: + st.mOrientation = Orientation.BOTTOM_TOP; + break; + case 135: + st.mOrientation = Orientation.BR_TL; + break; + case 180: + st.mOrientation = Orientation.RIGHT_LEFT; + break; + case 225: + st.mOrientation = Orientation.TR_BL; + break; + case 270: + st.mOrientation = Orientation.TOP_BOTTOM; + break; + case 315: + st.mOrientation = Orientation.TL_BR; + break; + } + } else { + final TypedValue tv = a.peekValue( + R.styleable.GradientDrawableGradient_gradientRadius); + if (tv != null) { + final float radius; + final int radiusType; + if (tv.type == TypedValue.TYPE_FRACTION) { + radius = tv.getFraction(1.0f, 1.0f); + + final int unit = (tv.data >> TypedValue.COMPLEX_UNIT_SHIFT) + & TypedValue.COMPLEX_UNIT_MASK; + if (unit == TypedValue.COMPLEX_UNIT_FRACTION_PARENT) { + radiusType = RADIUS_TYPE_FRACTION_PARENT; + } else { + radiusType = RADIUS_TYPE_FRACTION; + } + } else { + radius = tv.getDimension(r.getDisplayMetrics()); + radiusType = RADIUS_TYPE_PIXELS; + } + + st.mGradientRadius = radius; + st.mGradientRadiusType = radiusType; + } else if (st.mGradient == RADIAL_GRADIENT) { + throw new XmlPullParserException( + a.getPositionDescription() + + "<gradient> tag requires 'gradientRadius' " + + "attribute with radial type"); + } + } + + // Extract the theme attributes, if any. + mGradientState.mAttrGradient = a.extractThemeAttrs(); + } + + private void applyGradientDrawableSize(TypedArray a) { + int width = a.getDimensionPixelSize(R.styleable.GradientDrawableSize_width, -1); + int height = a.getDimensionPixelSize(R.styleable.GradientDrawableSize_height, -1); + setSize(width, height); + + // Extract the theme attributes, if any. + mGradientState.mAttrSize = a.extractThemeAttrs(); } private static float getFloatOrFraction(TypedArray a, int index, float defaultValue) { @@ -1322,6 +1508,14 @@ public class GradientDrawable extends Drawable { private boolean mUseLevelForShape; private boolean mOpaque; + int[] mThemeAttrs; + int[] mAttrSize; + int[] mAttrGradient; + int[] mAttrSolid; + int[] mAttrStroke; + int[] mAttrCorners; + int[] mAttrPadding; + GradientState(Orientation orientation, int[] colors) { mOrientation = orientation; setColors(colors); @@ -1363,16 +1557,33 @@ public class GradientDrawable extends Drawable { mUseLevel = state.mUseLevel; mUseLevelForShape = state.mUseLevelForShape; mOpaque = state.mOpaque; + mThemeAttrs = state.mThemeAttrs; + mAttrSize = state.mAttrSize; + mAttrGradient = state.mAttrGradient; + mAttrSolid = state.mAttrSolid; + mAttrStroke = state.mAttrStroke; + mAttrCorners = state.mAttrCorners; + mAttrPadding = state.mAttrPadding; + } + + @Override + public boolean canApplyTheme() { + return mThemeAttrs != null; } @Override public Drawable newDrawable() { - return new GradientDrawable(this); + return new GradientDrawable(this, null); } @Override public Drawable newDrawable(Resources res) { - return new GradientDrawable(this); + return new GradientDrawable(this, null); + } + + @Override + public Drawable newDrawable(Resources res, Theme theme) { + return new GradientDrawable(this, theme); } @Override @@ -1482,8 +1693,20 @@ public class GradientDrawable extends Drawable { } } - private GradientDrawable(GradientState state) { - mGradientState = state; + /** + * Creates a new themed GradientDrawable based on the specified constant state. + * <p> + * The resulting drawable is guaranteed to have a new constant state. + * + * @param state Constant state from which the drawable inherits + * @param theme Theme to apply to the drawable + */ + private GradientDrawable(GradientState state, Theme theme) { + mGradientState = new GradientState(state); + if (theme != null && state.canApplyTheme()) { + applyTheme(theme); + } + initializeWithState(state); mRectIsDirty = true; mMutated = false; diff --git a/graphics/java/android/graphics/drawable/InsetDrawable.java b/graphics/java/android/graphics/drawable/InsetDrawable.java index f841d6a..59a0c93 100644 --- a/graphics/java/android/graphics/drawable/InsetDrawable.java +++ b/graphics/java/android/graphics/drawable/InsetDrawable.java @@ -21,6 +21,7 @@ import org.xmlpull.v1.XmlPullParserException; import android.content.res.Resources; import android.content.res.TypedArray; +import android.content.res.Resources.Theme; import android.graphics.*; import android.util.AttributeSet; import android.util.Log; @@ -72,10 +73,10 @@ public class InsetDrawable extends Drawable implements Drawable.Callback drawable.setCallback(this); } } - - @Override public void inflate(Resources r, XmlPullParser parser, - AttributeSet attrs) - throws XmlPullParserException, IOException { + + @Override + public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) + throws XmlPullParserException, IOException { int type; TypedArray a = r.obtainAttributes(attrs, @@ -110,7 +111,7 @@ public class InsetDrawable extends Drawable implements Drawable.Callback + ": <inset> tag requires a 'drawable' attribute or " + "child tag defining a drawable"); } - dr = Drawable.createFromXmlInner(r, parser, attrs); + dr = Drawable.createFromXmlInnerThemed(r, parser, attrs, theme); } if (dr == null) { diff --git a/graphics/java/android/graphics/drawable/LayerDrawable.java b/graphics/java/android/graphics/drawable/LayerDrawable.java index 2e098a0..3d48cda 100644 --- a/graphics/java/android/graphics/drawable/LayerDrawable.java +++ b/graphics/java/android/graphics/drawable/LayerDrawable.java @@ -16,10 +16,8 @@ package android.graphics.drawable; -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; - import android.content.res.Resources; +import android.content.res.Resources.Theme; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.ColorFilter; @@ -28,6 +26,11 @@ import android.graphics.Rect; import android.util.AttributeSet; import android.view.View; +import com.android.internal.R; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + import java.io.IOException; /** @@ -84,7 +87,7 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { * @param state The constant drawable state. */ LayerDrawable(Drawable[] layers, LayerState state) { - this(state, null); + this(state, null, null); int length = layers.length; ChildDrawable[] r = new ChildDrawable[length]; @@ -101,15 +104,18 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { } LayerDrawable() { - this((LayerState) null, null); + this((LayerState) null, null, null); } - LayerDrawable(LayerState state, Resources res) { - LayerState as = createConstantState(state, res); + LayerDrawable(LayerState state, Resources res, Theme theme) { + final LayerState as = createConstantState(state, res); mLayerState = as; if (as.mNum > 0) { ensurePadding(); } + if (theme != null && canApplyTheme()) { + applyTheme(theme); + } } LayerState createConstantState(LayerState state, Resources res) { @@ -117,26 +123,53 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { } @Override - public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs) + public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) throws XmlPullParserException, IOException { - super.inflate(r, parser, attrs); + super.inflate(r, parser, attrs, theme); - int type; + final TypedArray a = obtainAttributes( + r, theme, attrs, R.styleable.LayerDrawable); + inflateStateFromTypedArray(a); + a.recycle(); - TypedArray a = r.obtainAttributes(attrs, com.android.internal.R.styleable.LayerDrawable); + inflateLayers(r, parser, attrs, theme); - mOpacityOverride = a.getInt(com.android.internal.R.styleable.LayerDrawable_opacity, - PixelFormat.UNKNOWN); + ensurePadding(); + onStateChange(getState()); + } + + /** + * Initializes the constant state from the values in the typed array. + */ + private void inflateStateFromTypedArray(TypedArray a) { + final LayerState state = mLayerState; - setAutoMirrored(a.getBoolean(com.android.internal.R.styleable.LayerDrawable_autoMirrored, - false)); + // Extract the theme attributes, if any. + final int[] themeAttrs = a.extractThemeAttrs(); + state.mThemeAttrs = themeAttrs; - mLayerState.mPaddingMode = a.getInteger( - com.android.internal.R.styleable.LayerDrawableItem_drawable, PADDING_MODE_NEST); + if (themeAttrs == null || themeAttrs[R.styleable.LayerDrawable_opacity] == 0) { + mOpacityOverride = a.getInt(R.styleable.LayerDrawable_opacity, PixelFormat.UNKNOWN); + } - a.recycle(); + if (themeAttrs == null || themeAttrs[R.styleable.LayerDrawable_autoMirrored] == 0) { + state.mAutoMirrored = a.getBoolean(R.styleable.LayerDrawable_autoMirrored, false); + } + if (themeAttrs == null || themeAttrs[R.styleable.LayerDrawableItem_drawable] == 0) { + state.mPaddingMode = a.getInteger( + R.styleable.LayerDrawableItem_drawable, PADDING_MODE_NEST); + } + } + + /** + * Inflates child layers using the specified parser. + */ + private void inflateLayers(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) + throws XmlPullParserException, IOException { + TypedArray a; final int innerDepth = parser.getDepth() + 1; + int type; int depth; while ((type = parser.next()) != XmlPullParser.END_DOCUMENT && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) { @@ -148,27 +181,28 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { continue; } - a = r.obtainAttributes(attrs, - com.android.internal.R.styleable.LayerDrawableItem); - - int left = a.getDimensionPixelOffset( - com.android.internal.R.styleable.LayerDrawableItem_left, 0); - int top = a.getDimensionPixelOffset( - com.android.internal.R.styleable.LayerDrawableItem_top, 0); - int right = a.getDimensionPixelOffset( - com.android.internal.R.styleable.LayerDrawableItem_right, 0); - int bottom = a.getDimensionPixelOffset( - com.android.internal.R.styleable.LayerDrawableItem_bottom, 0); - int drawableRes = a.getResourceId( - com.android.internal.R.styleable.LayerDrawableItem_drawable, 0); - int id = a.getResourceId(com.android.internal.R.styleable.LayerDrawableItem_id, - View.NO_ID); - + a = obtainAttributes( + r, theme, attrs, R.styleable.LayerDrawableItem); + + final int left = a.getDimensionPixelOffset( + R.styleable.LayerDrawableItem_left, 0); + final int top = a.getDimensionPixelOffset( + R.styleable.LayerDrawableItem_top, 0); + final int right = a.getDimensionPixelOffset( + R.styleable.LayerDrawableItem_right, 0); + final int bottom = a.getDimensionPixelOffset( + R.styleable.LayerDrawableItem_bottom, 0); + final int drawableRes = a.getResourceId( + R.styleable.LayerDrawableItem_drawable, 0); + final int id = a.getResourceId( + R.styleable.LayerDrawableItem_id, View.NO_ID); + + // TODO: Cache typed array, if necessary. a.recycle(); - Drawable dr; + final Drawable dr; if (drawableRes != 0) { - dr = r.getDrawable(drawableRes); + dr = r.getDrawable(drawableRes, theme); } else { while ((type = parser.next()) == XmlPullParser.TEXT) { } @@ -177,17 +211,87 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { + ": <item> tag requires a 'drawable' attribute or " + "child tag defining a drawable"); } - dr = Drawable.createFromXmlInner(r, parser, attrs); + dr = Drawable.createFromXmlInnerThemed(r, parser, attrs, theme); } addLayer(dr, id, left, top, right, bottom); } + } + + @Override + public void applyTheme(Theme t) { + super.applyTheme(t); + + final LayerState state = mLayerState; + if (state == null) { + throw new RuntimeException("Can't apply theme to <layer-list> with no constant state"); + } + + final int[] themeAttrs = state.mThemeAttrs; + if (themeAttrs != null) { + final TypedArray a = t.resolveAttributes(themeAttrs, R.styleable.LayerDrawable, 0, 0); + updateStateFromTypedArray(a); + a.recycle(); + } + + // TODO: Update layer positions from cached typed arrays. + + final ChildDrawable[] array = mLayerState.mChildren; + final int N = mLayerState.mNum; + for (int i = 0; i < N; i++) { + final Drawable layer = array[i].mDrawable; + if (layer.canApplyTheme()) { + layer.applyTheme(t); + } + } ensurePadding(); onStateChange(getState()); } /** + * Updates the constant state from the values in the typed array. + */ + private void updateStateFromTypedArray(TypedArray a) { + final LayerState state = mLayerState; + + if (a.hasValue(R.styleable.LayerDrawable_opacity)) { + mOpacityOverride = a.getInt(R.styleable.LayerDrawable_opacity, PixelFormat.UNKNOWN); + } + + if (a.hasValue(R.styleable.LayerDrawable_autoMirrored)) { + state.mAutoMirrored = a.getBoolean(R.styleable.LayerDrawable_autoMirrored, false); + } + + if (a.hasValue(R.styleable.LayerDrawableItem_drawable)) { + state.mPaddingMode = a.getInteger( + R.styleable.LayerDrawableItem_drawable, PADDING_MODE_NEST); + } + } + + @Override + public boolean canApplyTheme() { + final LayerState state = mLayerState; + if (state == null) { + return false; + } + + if (state.mThemeAttrs != null) { + return true; + } + + final ChildDrawable[] array = state.mChildren; + final int N = state.mNum; + for (int i = 0; i < N; i++) { + if (array[i].mDrawable.canApplyTheme()) { + return true; + } + } + + return false; + } + + /** * Add a new layer to this drawable. The new layer is identified by an id. * * @param layer The drawable to add as a layer. @@ -783,13 +887,35 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { static class ChildDrawable { public Drawable mDrawable; + public int[] mThemeAttrs; public int mInsetL, mInsetT, mInsetR, mInsetB; public int mId; + + ChildDrawable() { + // Default empty constructor. + } + + ChildDrawable(ChildDrawable or, LayerDrawable owner, Resources res) { + if (res != null) { + mDrawable = or.mDrawable.getConstantState().newDrawable(res); + } else { + mDrawable = or.mDrawable.getConstantState().newDrawable(); + } + mDrawable.setCallback(owner); + mDrawable.setLayoutDirection(or.mDrawable.getLayoutDirection()); + mThemeAttrs = or.mThemeAttrs; + mInsetL = or.mInsetL; + mInsetT = or.mInsetT; + mInsetR = or.mInsetR; + mInsetB = or.mInsetB; + mId = or.mId; + } } static class LayerState extends ConstantState { int mNum; ChildDrawable[] mChildren; + int[] mThemeAttrs; int mChangingConfigurations; int mChildrenChangingConfigurations; @@ -819,20 +945,8 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { mChildrenChangingConfigurations = orig.mChildrenChangingConfigurations; for (int i = 0; i < N; i++) { - final ChildDrawable r = mChildren[i] = new ChildDrawable(); final ChildDrawable or = origChildDrawable[i]; - if (res != null) { - r.mDrawable = or.mDrawable.getConstantState().newDrawable(res); - } else { - r.mDrawable = or.mDrawable.getConstantState().newDrawable(); - } - r.mDrawable.setCallback(owner); - r.mDrawable.setLayoutDirection(or.mDrawable.getLayoutDirection()); - r.mInsetL = or.mInsetL; - r.mInsetT = or.mInsetT; - r.mInsetR = or.mInsetR; - r.mInsetB = or.mInsetB; - r.mId = or.mId; + mChildren[i] = new ChildDrawable(or, owner, res); } mHaveOpacity = orig.mHaveOpacity; @@ -842,6 +956,7 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { mCheckedConstantState = mCanConstantState = true; mAutoMirrored = orig.mAutoMirrored; mPaddingMode = orig.mPaddingMode; + mThemeAttrs = orig.mThemeAttrs; } else { mNum = 0; mChildren = null; @@ -849,13 +964,23 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { } @Override + public boolean canApplyTheme() { + return mThemeAttrs != null; + } + + @Override public Drawable newDrawable() { - return new LayerDrawable(this, null); + return new LayerDrawable(this, null, null); } @Override public Drawable newDrawable(Resources res) { - return new LayerDrawable(this, res); + return new LayerDrawable(this, res, null); + } + + @Override + public Drawable newDrawable(Resources res, Theme theme) { + return new LayerDrawable(this, res, theme); } @Override diff --git a/graphics/java/android/graphics/drawable/LevelListDrawable.java b/graphics/java/android/graphics/drawable/LevelListDrawable.java index 872fdce..9f6c0ad 100644 --- a/graphics/java/android/graphics/drawable/LevelListDrawable.java +++ b/graphics/java/android/graphics/drawable/LevelListDrawable.java @@ -23,6 +23,7 @@ import org.xmlpull.v1.XmlPullParserException; import android.content.res.Resources; import android.content.res.TypedArray; +import android.content.res.Resources.Theme; import android.util.AttributeSet; /** @@ -83,10 +84,9 @@ public class LevelListDrawable extends DrawableContainer { } @Override - public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs) + public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) throws XmlPullParserException, IOException { - - super.inflate(r, parser, attrs); + super.inflate(r, parser, attrs, theme); int type; @@ -124,7 +124,7 @@ public class LevelListDrawable extends DrawableContainer { Drawable dr; if (drawableRes != 0) { - dr = r.getDrawable(drawableRes); + dr = r.getDrawable(drawableRes, theme); } else { while ((type = parser.next()) == XmlPullParser.TEXT) { } @@ -134,7 +134,7 @@ public class LevelListDrawable extends DrawableContainer { + ": <item> tag requires a 'drawable' attribute or " + "child tag defining a drawable"); } - dr = Drawable.createFromXmlInner(r, parser, attrs); + dr = Drawable.createFromXmlInnerThemed(r, parser, attrs, theme); } mLevelListState.addLevel(low, high, dr); diff --git a/graphics/java/android/graphics/drawable/NinePatchDrawable.java b/graphics/java/android/graphics/drawable/NinePatchDrawable.java index 44584a7..66193a5 100644 --- a/graphics/java/android/graphics/drawable/NinePatchDrawable.java +++ b/graphics/java/android/graphics/drawable/NinePatchDrawable.java @@ -18,6 +18,7 @@ package android.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.BitmapFactory; @@ -36,6 +37,8 @@ import android.util.DisplayMetrics; import android.util.LayoutDirection; import android.util.TypedValue; +import com.android.internal.R; + import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -73,6 +76,7 @@ public class NinePatchDrawable extends Drawable { private int mBitmapHeight; NinePatchDrawable() { + mNinePatchState = new NinePatchState(); } /** @@ -82,7 +86,7 @@ public class NinePatchDrawable extends Drawable { */ @Deprecated public NinePatchDrawable(Bitmap bitmap, byte[] chunk, Rect padding, String srcName) { - this(new NinePatchState(new NinePatch(bitmap, chunk, srcName), padding), null); + this(new NinePatchState(new NinePatch(bitmap, chunk, srcName), padding), null, null); } /** @@ -91,7 +95,7 @@ public class NinePatchDrawable extends Drawable { */ public NinePatchDrawable(Resources res, Bitmap bitmap, byte[] chunk, Rect padding, String srcName) { - this(new NinePatchState(new NinePatch(bitmap, chunk, srcName), padding), res); + this(new NinePatchState(new NinePatch(bitmap, chunk, srcName), padding), res, null); mNinePatchState.mTargetDensity = mTargetDensity; } @@ -103,7 +107,8 @@ public class NinePatchDrawable extends Drawable { */ public NinePatchDrawable(Resources res, Bitmap bitmap, byte[] chunk, Rect padding, Rect opticalInsets, String srcName) { - this(new NinePatchState(new NinePatch(bitmap, chunk, srcName), padding, opticalInsets), res); + this(new NinePatchState(new NinePatch(bitmap, chunk, srcName), padding, opticalInsets), + res, null); mNinePatchState.mTargetDensity = mTargetDensity; } @@ -114,7 +119,7 @@ public class NinePatchDrawable extends Drawable { */ @Deprecated public NinePatchDrawable(NinePatch patch) { - this(new NinePatchState(patch, new Rect()), null); + this(new NinePatchState(patch, new Rect()), null, null); } /** @@ -122,35 +127,10 @@ public class NinePatchDrawable extends Drawable { * based on the display metrics of the resources. */ public NinePatchDrawable(Resources res, NinePatch patch) { - this(new NinePatchState(patch, new Rect()), res); + this(new NinePatchState(patch, new Rect()), res, null); mNinePatchState.mTargetDensity = mTargetDensity; } - private void setNinePatchState(NinePatchState state, Resources res) { - mNinePatchState = state; - mNinePatch = state.mNinePatch; - mPadding = state.mPadding; - mTargetDensity = res != null ? res.getDisplayMetrics().densityDpi - : state.mTargetDensity; - //noinspection PointlessBooleanExpression - if (state.mDither != DEFAULT_DITHER) { - // avoid calling the setter unless we need to, since it does a - // lazy allocation of a paint - setDither(state.mDither); - } - - if (state.mTint != null) { - final int color = state.mTint.getColorForState(getState(), 0); - mTintFilter = new PorterDuffColorFilter(color, state.mTintMode); - } - - setAutoMirrored(state.mAutoMirrored); - - if (mNinePatch != null) { - computeBitmapSize(); - } - } - /** * Set the density scale at which this drawable will be rendered. This * method assumes the drawable will be rendered at the same density as the @@ -228,6 +208,19 @@ public class NinePatchDrawable extends Drawable { } } + private void setNinePatch(NinePatch ninePatch) { + if (ninePatch != mNinePatch) { + mNinePatch = ninePatch; + if (ninePatch != null) { + computeBitmapSize(); + } else { + mBitmapWidth = mBitmapHeight = -1; + mOpticalInsets = Insets.NONE; + } + invalidateSelf(); + } + } + @Override public void draw(Canvas canvas) { final Rect bounds = getBounds(); @@ -266,12 +259,17 @@ public class NinePatchDrawable extends Drawable { @Override public boolean getPadding(Rect padding) { - if (needsMirroring()) { - padding.set(mPadding.right, mPadding.top, mPadding.left, mPadding.bottom); - } else { - padding.set(mPadding); + final Rect scaledPadding = mPadding; + if (scaledPadding != null) { + if (needsMirroring()) { + padding.set(scaledPadding.right, scaledPadding.top, + scaledPadding.left, scaledPadding.bottom); + } else { + padding.set(scaledPadding); + } + return (padding.left | padding.top | padding.right | padding.bottom) != 0; } - return (padding.left | padding.top | padding.right | padding.bottom) != 0; + return false; } /** @@ -408,70 +406,195 @@ public class NinePatchDrawable extends Drawable { } @Override - public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs) + public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) throws XmlPullParserException, IOException { - super.inflate(r, parser, attrs); + super.inflate(r, parser, attrs, theme); + + final TypedArray a = obtainAttributes( + r, theme, attrs, R.styleable.NinePatchDrawable); + inflateStateFromTypedArray(a); + a.recycle(); + } + + /** + * Initializes the constant state from the values in the typed array. + */ + private void inflateStateFromTypedArray(TypedArray a) throws XmlPullParserException { + final Resources r = a.getResources(); + final NinePatchState ninePatchState = mNinePatchState; - TypedArray a = r.obtainAttributes(attrs, com.android.internal.R.styleable.NinePatchDrawable); + // Extract the theme attributes, if any. + final int[] themeAttrs = a.extractThemeAttrs(); + ninePatchState.mThemeAttrs = themeAttrs; - final int id = a.getResourceId(com.android.internal.R.styleable.NinePatchDrawable_src, 0); - if (id == 0) { - throw new XmlPullParserException(parser.getPositionDescription() + - ": <nine-patch> requires a valid src attribute"); + if (themeAttrs == null || themeAttrs[R.styleable.NinePatchDrawable_dither] == 0) { + final boolean dither = a.getBoolean( + R.styleable.NinePatchDrawable_dither, DEFAULT_DITHER); + ninePatchState.mDither = dither; } - final boolean dither = a.getBoolean( - com.android.internal.R.styleable.NinePatchDrawable_dither, DEFAULT_DITHER); - final BitmapFactory.Options options = new BitmapFactory.Options(); - if (dither) { - options.inDither = false; + if (themeAttrs == null || themeAttrs[R.styleable.NinePatchDrawable_src] == 0) { + final int id = a.getResourceId(R.styleable.NinePatchDrawable_src, 0); + if (id == 0) { + throw new XmlPullParserException(a.getPositionDescription() + + ": <nine-patch> requires a valid src attribute"); + } + + final BitmapFactory.Options options = new BitmapFactory.Options(); + options.inDither = !ninePatchState.mDither; + options.inScreenDensity = r.getDisplayMetrics().noncompatDensityDpi; + + final Rect padding = new Rect(); + final Rect opticalInsets = new Rect(); + Bitmap bitmap = null; + + try { + final TypedValue value = new TypedValue(); + final InputStream is = r.openRawResource(id, value); + + bitmap = BitmapFactory.decodeResourceStream(r, value, is, padding, options); + + is.close(); + } catch (IOException e) { + // Ignore + } + + if (bitmap == null) { + throw new XmlPullParserException(a.getPositionDescription() + + ": <nine-patch> requires a valid src attribute"); + } else if (bitmap.getNinePatchChunk() == null) { + throw new XmlPullParserException(a.getPositionDescription() + + ": <nine-patch> requires a valid 9-patch source image"); + } + + final NinePatch ninePatch = new NinePatch(bitmap, bitmap.getNinePatchChunk()); + ninePatchState.mNinePatch = ninePatch; + ninePatchState.mPadding = padding; + ninePatchState.mOpticalInsets = Insets.of(opticalInsets); } - options.inScreenDensity = r.getDisplayMetrics().noncompatDensityDpi; - final Rect padding = new Rect(); - final Rect opticalInsets = new Rect(); - Bitmap bitmap = null; + if (themeAttrs == null || themeAttrs[R.styleable.NinePatchDrawable_autoMirrored] == 0) { + final boolean autoMirrored = a.getBoolean( + R.styleable.NinePatchDrawable_autoMirrored, false); + ninePatchState.mAutoMirrored = autoMirrored; + } - try { - final TypedValue value = new TypedValue(); - final InputStream is = r.openRawResource(id, value); + if (themeAttrs == null || themeAttrs[R.styleable.NinePatchDrawable_tintMode] == 0) { + final int tintModeValue = a.getInt(R.styleable.NinePatchDrawable_tintMode, -1); + ninePatchState.mTintMode = Drawable.parseTintMode(tintModeValue, Mode.SRC_IN); + } - bitmap = BitmapFactory.decodeResourceStream(r, value, is, padding, options); + if (themeAttrs == null || themeAttrs[R.styleable.NinePatchDrawable_tint] == 0) { + ninePatchState.mTint = a.getColorStateList(R.styleable.NinePatchDrawable_tint); + if (ninePatchState.mTint != null) { + final int color = ninePatchState.mTint.getColorForState(getState(), 0); + mTintFilter = new PorterDuffColorFilter(color, ninePatchState.mTintMode); + } + } - is.close(); - } catch (IOException e) { - // Ignore + // Apply the constant state to the paint. + initializeWithState(ninePatchState, r); + + // Push density applied by setNinePatchState into state. + ninePatchState.mTargetDensity = mTargetDensity; + } + + @Override + public void applyTheme(Theme t) { + super.applyTheme(t); + + final NinePatchState state = mNinePatchState; + if (state == null) { + throw new RuntimeException("Can't apply theme to <nine-patch> with no constant state"); } - if (bitmap == null) { - throw new XmlPullParserException(parser.getPositionDescription() + - ": <nine-patch> requires a valid src attribute"); - } else if (bitmap.getNinePatchChunk() == null) { - throw new XmlPullParserException(parser.getPositionDescription() + - ": <nine-patch> requires a valid 9-patch source image"); + final int[] themeAttrs = state.mThemeAttrs; + if (themeAttrs != null) { + final TypedArray a = t.resolveAttributes( + themeAttrs, R.styleable.NinePatchDrawable, 0, 0); + updateStateFromTypedArray(a); + a.recycle(); } + } - final boolean automirrored = a.getBoolean( - com.android.internal.R.styleable.NinePatchDrawable_autoMirrored, false); - final NinePatchState ninePatchState = new NinePatchState( - new NinePatch(bitmap, bitmap.getNinePatchChunk()), padding, opticalInsets, dither, - automirrored); + /** + * Updates the constant state from the values in the typed array. + */ + private void updateStateFromTypedArray(TypedArray a) { + final Resources r = a.getResources(); + final NinePatchState state = mNinePatchState; - final int tintModeValue = a.getInt( - com.android.internal.R.styleable.NinePatchDrawable_tintMode, -1); - ninePatchState.mTintMode = Drawable.parseTintMode(tintModeValue, Mode.SRC_IN); - ninePatchState.mTint = a.getColorStateList( - com.android.internal.R.styleable.NinePatchDrawable_tint); - if (ninePatchState.mTint != null) { - final int color = ninePatchState.mTint.getColorForState(getState(), 0); - mTintFilter = new PorterDuffColorFilter(color, ninePatchState.mTintMode); + if (a.hasValue(R.styleable.NinePatchDrawable_dither)) { + state.mDither = a.getBoolean(R.styleable.NinePatchDrawable_dither, DEFAULT_DITHER); } - setNinePatchState(ninePatchState, r); + if (a.hasValue(R.styleable.NinePatchDrawable_autoMirrored)) { + state.mAutoMirrored = a.getBoolean(R.styleable.NinePatchDrawable_autoMirrored, false); + } - mNinePatchState.mTargetDensity = mTargetDensity; + if (a.hasValue(R.styleable.NinePatchDrawable_src)) { + final int id = a.getResourceId(R.styleable.NinePatchDrawable_src, 0); + if (id == 0) { + throw new RuntimeException(a.getPositionDescription() + + ": <nine-patch> requires a valid src attribute"); + } - a.recycle(); + final BitmapFactory.Options options = new BitmapFactory.Options(); + options.inDither = !state.mDither; + options.inScreenDensity = r.getDisplayMetrics().noncompatDensityDpi; + + final Rect padding = new Rect(); + final Rect opticalInsets = new Rect(); + Bitmap bitmap = null; + + try { + final TypedValue value = new TypedValue(); + final InputStream is = r.openRawResource(id, value); + + bitmap = BitmapFactory.decodeResourceStream(r, value, is, padding, options); + + is.close(); + } catch (IOException e) { + // Ignore + } + + if (bitmap == null) { + throw new RuntimeException(a.getPositionDescription() + + ": <nine-patch> requires a valid src attribute"); + } else if (bitmap.getNinePatchChunk() == null) { + throw new RuntimeException(a.getPositionDescription() + + ": <nine-patch> requires a valid 9-patch source image"); + } + + state.mNinePatch = new NinePatch(bitmap, bitmap.getNinePatchChunk()); + state.mPadding = padding; + state.mOpticalInsets = Insets.of(opticalInsets); + } + + if (a.hasValue(R.styleable.NinePatchDrawable_tintMode)) { + final int modeValue = a.getInt(R.styleable.NinePatchDrawable_tintMode, -1); + state.mTintMode = Drawable.parseTintMode(modeValue, Mode.SRC_IN); + } + + if (a.hasValue(R.styleable.NinePatchDrawable_tint)) { + final ColorStateList tint = a.getColorStateList(R.styleable.NinePatchDrawable_tint); + if (tint != null) { + state.mTint = tint; + final int color = tint.getColorForState(getState(), 0); + mTintFilter = new PorterDuffColorFilter(color, state.mTintMode); + } + } + + // Apply the constant state to the paint. + initializeWithState(state, r); + + // Push density applied by setNinePatchState into state. + state.mTargetDensity = mTargetDensity; + } + + @Override + public boolean canApplyTheme() { + return mNinePatchState != null && mNinePatchState.mThemeAttrs != null; } public Paint getPaint() { @@ -568,9 +691,14 @@ public class NinePatchDrawable extends Drawable { Rect mPadding; Insets mOpticalInsets; boolean mDither; + int[] mThemeAttrs; int mChangingConfigurations; int mTargetDensity = DisplayMetrics.DENSITY_DEFAULT; boolean mAutoMirrored; + + NinePatchState() { + // Empty constructor. + } NinePatchState(NinePatch ninePatch, Rect padding) { this(ninePatch, padding, new Rect(), DEFAULT_DITHER, false); @@ -581,7 +709,7 @@ public class NinePatchDrawable extends Drawable { } NinePatchState(NinePatch ninePatch, Rect rect, Rect opticalInsets, boolean dither, - boolean autoMirror) { + boolean autoMirror) { mNinePatch = ninePatch; mPadding = rect; mOpticalInsets = Insets.of(opticalInsets); @@ -596,6 +724,7 @@ public class NinePatchDrawable extends Drawable { mNinePatch = state.mNinePatch; mTint = state.mTint; mTintMode = state.mTintMode; + mThemeAttrs = state.mThemeAttrs; mPadding = state.mPadding; mOpticalInsets = state.mOpticalInsets; mDither = state.mDither; @@ -605,18 +734,28 @@ public class NinePatchDrawable extends Drawable { } @Override + public boolean canApplyTheme() { + return mThemeAttrs != null; + } + + @Override public Bitmap getBitmap() { return mNinePatch.getBitmap(); } @Override public Drawable newDrawable() { - return new NinePatchDrawable(this, null); + return new NinePatchDrawable(this, null, null); } @Override public Drawable newDrawable(Resources res) { - return new NinePatchDrawable(this, res); + return new NinePatchDrawable(this, res, null); + } + + @Override + public Drawable newDrawable(Resources res, Theme theme) { + return new NinePatchDrawable(this, res, theme); } @Override @@ -625,7 +764,40 @@ public class NinePatchDrawable extends Drawable { } } - private NinePatchDrawable(NinePatchState state, Resources res) { - setNinePatchState(state, res); + private NinePatchDrawable(NinePatchState state, Resources res, Theme theme) { + if (theme != null && state.canApplyTheme()) { + mNinePatchState = new NinePatchState(state); + applyTheme(theme); + } else { + mNinePatchState = state; + } + + initializeWithState(state, res); + } + + /** + * Initializes local dynamic properties from state. + */ + private void initializeWithState(NinePatchState state, Resources res) { + if (res != null) { + mTargetDensity = res.getDisplayMetrics().densityDpi; + } else { + mTargetDensity = state.mTargetDensity; + } + + // If we can, avoid calling any methods that initialize Paint. + if (state.mDither != DEFAULT_DITHER) { + setDither(state.mDither); + } + + if (state.mTint != null) { + final int color = state.mTint.getColorForState(getState(), 0); + mTintFilter = new PorterDuffColorFilter(color, state.mTintMode); + } + + final Rect statePadding = state.mPadding; + mPadding = statePadding != null ? new Rect(statePadding) : null; + + setNinePatch(state.mNinePatch); } } diff --git a/graphics/java/android/graphics/drawable/RevealDrawable.java b/graphics/java/android/graphics/drawable/RevealDrawable.java index 91de638..2f96fe4 100644 --- a/graphics/java/android/graphics/drawable/RevealDrawable.java +++ b/graphics/java/android/graphics/drawable/RevealDrawable.java @@ -17,6 +17,7 @@ package android.graphics.drawable; import android.content.res.Resources; +import android.content.res.Resources.Theme; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.PorterDuff.Mode; @@ -88,11 +89,15 @@ public class RevealDrawable extends LayerDrawable { * @see #RevealDrawable(Drawable[]) */ RevealDrawable() { - this(new RevealState(null, null, null), (Resources) null); + this(new RevealState(null, null, null), (Resources) null, null); } private RevealDrawable(RevealState state, Resources res) { - super(state, res); + super(state, res, null); + } + + private RevealDrawable(RevealState state, Resources res, Theme theme) { + super(state, res, theme); } private RevealDrawable(RevealState state, Drawable[] layers) { @@ -100,9 +105,9 @@ public class RevealDrawable extends LayerDrawable { } @Override - public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs) + public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) throws XmlPullParserException, IOException { - super.inflate(r, parser, attrs); + super.inflate(r, parser, attrs, theme); setTargetDensity(r.getDisplayMetrics()); setPaddingMode(PADDING_MODE_STACK); @@ -303,5 +308,10 @@ public class RevealDrawable extends LayerDrawable { public Drawable newDrawable(Resources res) { return new RevealDrawable(this, res); } + + @Override + public Drawable newDrawable(Resources res, Theme theme) { + return new RevealDrawable(this, res, theme); + } } } diff --git a/graphics/java/android/graphics/drawable/RotateDrawable.java b/graphics/java/android/graphics/drawable/RotateDrawable.java index 630dc2e..edf1091 100644 --- a/graphics/java/android/graphics/drawable/RotateDrawable.java +++ b/graphics/java/android/graphics/drawable/RotateDrawable.java @@ -24,6 +24,7 @@ import android.graphics.ColorFilter; import android.graphics.Rect; import android.content.res.Resources; import android.content.res.TypedArray; +import android.content.res.Resources.Theme; import android.util.TypedValue; import android.util.AttributeSet; import android.util.Log; @@ -358,7 +359,7 @@ public class RotateDrawable extends Drawable implements Drawable.Callback { } @Override - public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs) + public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) throws XmlPullParserException, IOException { final TypedArray a = r.obtainAttributes(attrs, com.android.internal.R.styleable.RotateDrawable); @@ -411,7 +412,7 @@ public class RotateDrawable extends Drawable implements Drawable.Callback { continue; } - if ((drawable = Drawable.createFromXmlInner(r, parser, attrs)) == null) { + if ((drawable = Drawable.createFromXmlInnerThemed(r, parser, attrs, theme)) == null) { Log.w("drawable", "Bad element under <rotate>: " + parser .getName()); } diff --git a/graphics/java/android/graphics/drawable/ScaleDrawable.java b/graphics/java/android/graphics/drawable/ScaleDrawable.java index ec6b2c1..4c4d9af 100644 --- a/graphics/java/android/graphics/drawable/ScaleDrawable.java +++ b/graphics/java/android/graphics/drawable/ScaleDrawable.java @@ -21,6 +21,7 @@ import org.xmlpull.v1.XmlPullParserException; import android.content.res.Resources; import android.content.res.TypedArray; +import android.content.res.Resources.Theme; import android.graphics.*; import android.view.Gravity; import android.util.AttributeSet; @@ -84,9 +85,9 @@ public class ScaleDrawable extends Drawable implements Drawable.Callback { } @Override - public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs) + public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) throws XmlPullParserException, IOException { - super.inflate(r, parser, attrs); + super.inflate(r, parser, attrs, theme); int type; @@ -107,7 +108,7 @@ public class ScaleDrawable extends Drawable implements Drawable.Callback { if (type != XmlPullParser.START_TAG) { continue; } - dr = Drawable.createFromXmlInner(r, parser, attrs); + dr = Drawable.createFromXmlInnerThemed(r, parser, attrs, theme); } if (dr == null) { diff --git a/graphics/java/android/graphics/drawable/ShapeDrawable.java b/graphics/java/android/graphics/drawable/ShapeDrawable.java index 16de9f3..96309f9 100644 --- a/graphics/java/android/graphics/drawable/ShapeDrawable.java +++ b/graphics/java/android/graphics/drawable/ShapeDrawable.java @@ -28,6 +28,7 @@ import android.graphics.PorterDuffColorFilter; import android.graphics.Rect; import android.graphics.Shader; import android.graphics.drawable.shapes.Shape; +import android.content.res.Resources.Theme; import android.util.AttributeSet; import org.xmlpull.v1.XmlPullParser; @@ -443,9 +444,9 @@ public class ShapeDrawable extends Drawable { } @Override - public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs) + public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) throws XmlPullParserException, IOException { - super.inflate(r, parser, attrs); + super.inflate(r, parser, attrs, theme); TypedArray a = r.obtainAttributes(attrs, com.android.internal.R.styleable.ShapeDrawable); diff --git a/graphics/java/android/graphics/drawable/StateListDrawable.java b/graphics/java/android/graphics/drawable/StateListDrawable.java index 48d66b7..271af2b 100644 --- a/graphics/java/android/graphics/drawable/StateListDrawable.java +++ b/graphics/java/android/graphics/drawable/StateListDrawable.java @@ -24,6 +24,7 @@ import java.util.Arrays; import android.content.res.Resources; import android.content.res.TypedArray; +import android.content.res.Resources.Theme; import android.util.AttributeSet; import android.util.StateSet; @@ -110,8 +111,7 @@ public class StateListDrawable extends DrawableContainer { } @Override - public void inflate(Resources r, XmlPullParser parser, - AttributeSet attrs) + public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) throws XmlPullParserException, IOException { TypedArray a = r.obtainAttributes(attrs, @@ -183,7 +183,7 @@ public class StateListDrawable extends DrawableContainer { + ": <item> tag requires a 'drawable' attribute or " + "child tag defining a drawable"); } - dr = Drawable.createFromXmlInner(r, parser, attrs); + dr = Drawable.createFromXmlInnerThemed(r, parser, attrs, theme); } mStateListState.addStateSet(states, dr); diff --git a/graphics/java/android/graphics/drawable/TouchFeedbackDrawable.java b/graphics/java/android/graphics/drawable/TouchFeedbackDrawable.java index f4f545c..ef91494 100644 --- a/graphics/java/android/graphics/drawable/TouchFeedbackDrawable.java +++ b/graphics/java/android/graphics/drawable/TouchFeedbackDrawable.java @@ -18,6 +18,7 @@ package android.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.Canvas; import android.graphics.Color; @@ -78,20 +79,10 @@ public class TouchFeedbackDrawable extends DrawableWrapper { /** The drawable to use as the mask. */ private Drawable mMask; - /* package */TouchFeedbackDrawable() { - this(null, null); + TouchFeedbackDrawable() { + this(new TouchFeedbackState(null), null, null); } - TouchFeedbackDrawable(TouchFeedbackState state, Resources res) { - mState = new TouchFeedbackState(state); - - setConstantState(mState, res); - - if (res != null) { - mDensity = res.getDisplayMetrics().density; - } - } - private void setConstantState(TouchFeedbackState wrapperState, Resources res) { super.setConstantState(wrapperState, res); @@ -103,6 +94,10 @@ public class TouchFeedbackDrawable extends DrawableWrapper { } else { mMask = wrapperState.mMaskState.newDrawable(); } + + if (res != null) { + mDensity = res.getDisplayMetrics().density; + } } @Override @@ -152,60 +147,31 @@ public class TouchFeedbackDrawable extends DrawableWrapper { } @Override - public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs) + public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) throws XmlPullParserException, IOException { - super.inflate(r, parser, attrs); - - final TypedArray a = r.obtainAttributes(attrs, R.styleable.TouchFeedbackDrawable); + super.inflate(r, parser, attrs, theme); - mState.mTint = a.getColorStateList(R.styleable.TouchFeedbackDrawable_tint); - mState.mTintMode = Drawable.parseTintMode( - a.getInt(R.styleable.TouchFeedbackDrawable_tintMode, -1), Mode.SRC_ATOP); - mState.mPinned = a.getBoolean(R.styleable.TouchFeedbackDrawable_pinned, false); - - if (mState.mTint == null) { - throw new XmlPullParserException(parser.getPositionDescription() - + ": <touch-feedback> tag requires a 'tint' attribute"); - } - - Drawable mask = a.getDrawable(R.styleable.TouchFeedbackDrawable_mask); - final int drawableRes = a.getResourceId(R.styleable.TouchFeedbackDrawable_drawable, 0); + final TypedArray a = obtainAttributes( + r, theme, attrs, R.styleable.TouchFeedbackDrawable); + inflateStateFromTypedArray(r, a); a.recycle(); + + inflateChildElements(r, parser, attrs, theme); - final Drawable dr; - if (drawableRes != 0) { - dr = r.getDrawable(drawableRes); - } else { - int type; - while ((type = parser.next()) == XmlPullParser.TEXT) { - // Find the next non-text element. - } - - if (type == XmlPullParser.START_TAG) { - dr = Drawable.createFromXmlInner(r, parser, attrs); - } else { - dr = null; - } - } - - // If no mask is set, implicitly use the lower drawable. - if (mask == null) { - mask = dr; + setTargetDensity(r.getDisplayMetrics()); + } + + private void inflateChildElements(Resources r, XmlPullParser parser, AttributeSet attrs, + Theme theme) throws XmlPullParserException, IOException { + int type; + while ((type = parser.next()) == XmlPullParser.TEXT) { + // Find the next non-text element. } - // If neither a mask not a bottom layer was specified, assume we're - // projecting onto a parent surface. - mState.mProjected = mask == null && dr == null; - - if (dr != null) { + if (type == XmlPullParser.START_TAG) { + final Drawable dr = Drawable.createFromXmlInner(r, parser, attrs); setDrawable(dr, r); - } else { - // For now at least, we MUST have a wrapped drawable. - setDrawable(new ColorDrawable(Color.TRANSPARENT), r); } - - setMaskDrawable(mask, r); - setTargetDensity(r.getDisplayMetrics()); } /** @@ -228,6 +194,62 @@ public class TouchFeedbackDrawable extends DrawableWrapper { } /** + * Initializes the constant state from the values in the typed array. + */ + private void inflateStateFromTypedArray(Resources r, TypedArray a) { + final TouchFeedbackState state = mState; + + // Extract the theme attributes, if any. + final int[] themeAttrs = a.extractThemeAttrs(); + state.mThemeAttrs = themeAttrs; + + if (themeAttrs == null || themeAttrs[R.styleable.TouchFeedbackDrawable_tint] == 0) { + mState.mTint = a.getColorStateList(R.styleable.TouchFeedbackDrawable_tint); + + if (mState.mTint == null) { + throw new RuntimeException("<touch-feedback> tag requires a 'tint' attribute"); + } + } + + if (themeAttrs == null || themeAttrs[R.styleable.TouchFeedbackDrawable_tintMode] == 0) { + mState.mTintMode = Drawable.parseTintMode( + a.getInt(R.styleable.TouchFeedbackDrawable_tintMode, -1), Mode.SRC_ATOP); + } + + if (themeAttrs == null || themeAttrs[R.styleable.TouchFeedbackDrawable_pinned] == 0) { + mState.mPinned = a.getBoolean(R.styleable.TouchFeedbackDrawable_pinned, false); + } + + Drawable mask = mMask; + if (themeAttrs == null || themeAttrs[R.styleable.TouchFeedbackDrawable_mask] == 0) { + mask = a.getDrawable(R.styleable.TouchFeedbackDrawable_mask); + } + + Drawable dr = super.getDrawable(); + if (themeAttrs == null || themeAttrs[R.styleable.TouchFeedbackDrawable_drawable] == 0) { + final int drawableRes = a.getResourceId(R.styleable.TouchFeedbackDrawable_drawable, 0); + if (drawableRes != 0) { + dr = r.getDrawable(drawableRes); + } + } + + // If neither a mask not a bottom layer was specified, assume we're + // projecting onto a parent surface. + mState.mProjected = mask == null && dr == null; + + if (dr != null) { + setDrawable(dr, r); + } else { + // For now at least, we MUST have a wrapped drawable. + setDrawable(new ColorDrawable(Color.TRANSPARENT), r); + } + + if (mask != null) { + setMaskDrawable(mask, r); + } + } + + /** * Set the density at which this drawable will be rendered. * * @param metrics The display metrics for this drawable. @@ -239,6 +261,78 @@ public class TouchFeedbackDrawable extends DrawableWrapper { } } + @Override + public void applyTheme(Theme t) { + super.applyTheme(t); + + final TouchFeedbackState state = mState; + if (state == null) { + throw new RuntimeException( + "Can't apply theme to <touch-feedback> with no constant state"); + } + + final int[] themeAttrs = state.mThemeAttrs; + if (themeAttrs != null) { + final TypedArray a = t.resolveAttributes( + themeAttrs, R.styleable.TouchFeedbackDrawable, 0, 0); + updateStateFromTypedArray(a); + a.recycle(); + } + } + + /** + * Updates the constant state from the values in the typed array. + */ + private void updateStateFromTypedArray(TypedArray a) { + final TouchFeedbackState state = mState; + + if (a.hasValue(R.styleable.TouchFeedbackDrawable_tint)) { + state.mTint = a.getColorStateList(R.styleable.TouchFeedbackDrawable_tint); + } + + if (a.hasValue(R.styleable.TouchFeedbackDrawable_tintMode)) { + mState.mTintMode = Drawable.parseTintMode( + a.getInt(R.styleable.TouchFeedbackDrawable_tintMode, -1), Mode.SRC_ATOP); + } + + if (a.hasValue(R.styleable.TouchFeedbackDrawable_pinned)) { + mState.mPinned = a.getBoolean(R.styleable.TouchFeedbackDrawable_pinned, false); + } + + Drawable mask = mMask; + if (a.hasValue(R.styleable.TouchFeedbackDrawable_mask)) { + mask = a.getDrawable(R.styleable.TouchFeedbackDrawable_mask); + } + + Drawable dr = super.getDrawable(); + if (a.hasValue(R.styleable.TouchFeedbackDrawable_drawable)) { + final int drawableRes = a.getResourceId(R.styleable.TouchFeedbackDrawable_drawable, 0); + if (drawableRes != 0) { + dr = a.getResources().getDrawable(drawableRes); + } + } + + // If neither a mask not a bottom layer was specified, assume we're + // projecting onto a parent surface. + mState.mProjected = mask == null && dr == null; + + if (dr != null) { + setDrawable(dr, a.getResources()); + } else { + // For now at least, we MUST have a wrapped drawable. + setDrawable(new ColorDrawable(Color.TRANSPARENT), a.getResources()); + } + + if (mask != null) { + setMaskDrawable(mask, a.getResources()); + } + } + + @Override + public boolean canApplyTheme() { + return mState != null && mState.mThemeAttrs != null; + } + /** * @hide until hotspot APIs are finalized */ @@ -333,7 +427,7 @@ public class TouchFeedbackDrawable extends DrawableWrapper { if (mAnimationRunnable == null) { mAnimationRunnable = new Runnable() { - @Override + @Override public void run() { mAnimating = false; scheduleAnimation(); @@ -357,7 +451,7 @@ public class TouchFeedbackDrawable extends DrawableWrapper { } final ArrayList<Ripple> activeRipples = mActiveRipples; - final Drawable mask = mMask; + final Drawable mask = mMask == null && !mState.mProjected ? getDrawable() : null; final Rect bounds = mask == null ? null : mask.getBounds(); // Draw ripples into a layer that merges using SRC_IN. @@ -450,6 +544,7 @@ public class TouchFeedbackDrawable extends DrawableWrapper { } static class TouchFeedbackState extends WrapperState { + int[] mThemeAttrs; ConstantState mMaskState; ColorStateList mTint; Mode mTintMode; @@ -460,6 +555,7 @@ public class TouchFeedbackDrawable extends DrawableWrapper { super(orig); if (orig != null) { + mThemeAttrs = orig.mThemeAttrs; mTint = orig.mTint; mTintMode = orig.mTintMode; mMaskState = orig.mMaskState; @@ -469,13 +565,34 @@ public class TouchFeedbackDrawable extends DrawableWrapper { } @Override + public boolean canApplyTheme() { + return mThemeAttrs != null; + } + + @Override public Drawable newDrawable() { - return new TouchFeedbackDrawable(this, null); + return new TouchFeedbackDrawable(this, null, null); } @Override public Drawable newDrawable(Resources res) { - return new TouchFeedbackDrawable(this, res); + return new TouchFeedbackDrawable(this, res, null); + } + + @Override + public Drawable newDrawable(Resources res, Theme theme) { + return new TouchFeedbackDrawable(this, res, theme); } } + + private TouchFeedbackDrawable(TouchFeedbackState state, Resources res, Theme theme) { + if (theme != null && state.canApplyTheme()) { + mState = new TouchFeedbackState(state); + applyTheme(theme); + } else { + mState = state; + } + + setConstantState(state, res); + } } diff --git a/graphics/java/android/graphics/drawable/TransitionDrawable.java b/graphics/java/android/graphics/drawable/TransitionDrawable.java index 483fa56..622e90b 100644 --- a/graphics/java/android/graphics/drawable/TransitionDrawable.java +++ b/graphics/java/android/graphics/drawable/TransitionDrawable.java @@ -17,6 +17,7 @@ package android.graphics.drawable; import android.content.res.Resources; +import android.content.res.Resources.Theme; import android.graphics.Canvas; import android.os.SystemClock; @@ -85,11 +86,11 @@ public class TransitionDrawable extends LayerDrawable implements Drawable.Callba * @see #TransitionDrawable(Drawable[]) */ TransitionDrawable() { - this(new TransitionState(null, null, null), (Resources)null); + this(new TransitionState(null, null, null), null, null); } - private TransitionDrawable(TransitionState state, Resources res) { - super(state, res); + private TransitionDrawable(TransitionState state, Resources res, Theme theme) { + super(state, res, theme); } private TransitionDrawable(TransitionState state, Drawable[] layers) { @@ -251,12 +252,17 @@ public class TransitionDrawable extends LayerDrawable implements Drawable.Callba @Override public Drawable newDrawable() { - return new TransitionDrawable(this, (Resources)null); + return new TransitionDrawable(this, null, null); } @Override public Drawable newDrawable(Resources res) { - return new TransitionDrawable(this, res); + return new TransitionDrawable(this, res, null); + } + + @Override + public Drawable newDrawable(Resources res, Theme theme) { + return new TransitionDrawable(this, res, theme); } @Override |