From 7e3ede288926bdfb79b1571fed74cad614935821 Mon Sep 17 00:00:00 2001 From: Alan Viverette Date: Wed, 28 Oct 2015 16:57:57 -0400 Subject: [PATCH] Support for changing density of DrawableWrapper subclasses Includes a refactoring of DrawableWrapper classes so that the wrapper super class handles both drawable management and inflation. This allows us to immediately call through to super() in inflate and applyTheme, which simplifies density management. Bug: 25081461 Change-Id: I8c157d340fd1f28a3a2b786c56850a67cdd452e4 --- core/res/res/values/attrs.xml | 7 + .../drawable/AnimatedRotateDrawable.java | 104 ++++++----- .../graphics/drawable/ClipDrawable.java | 84 +++++---- .../android/graphics/drawable/Drawable.java | 28 ++- .../graphics/drawable/DrawableWrapper.java | 136 +++++++++++--- .../graphics/drawable/InsetDrawable.java | 170 +++++++++++------- .../graphics/drawable/RotateDrawable.java | 84 ++++----- .../graphics/drawable/ScaleDrawable.java | 95 +++++----- 8 files changed, 436 insertions(+), 272 deletions(-) diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 093ea80950b7e..d7dd3ec92a561 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -5018,6 +5018,12 @@ i + + + + + + @@ -5385,6 +5391,7 @@ i + diff --git a/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java b/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java index 4fc5edee94c68..971a3a295ca2d 100644 --- a/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java +++ b/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java @@ -50,7 +50,7 @@ public class AnimatedRotateDrawable extends DrawableWrapper implements Animatabl * Creates a new animated rotating drawable with no wrapped drawable. */ public AnimatedRotateDrawable() { - this(new AnimatedRotateState(null), null); + this(new AnimatedRotateState(null, null), null); } @Override @@ -126,57 +126,21 @@ public class AnimatedRotateDrawable extends DrawableWrapper implements Animatabl @NonNull AttributeSet attrs, @Nullable Theme theme) throws XmlPullParserException, IOException { final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.AnimatedRotateDrawable); - super.inflateWithAttributes(r, parser, a, R.styleable.AnimatedRotateDrawable_visible); + + // Inflation will advance the XmlPullParser and AttributeSet. + super.inflate(r, parser, attrs, theme); updateStateFromTypedArray(a); - inflateChildDrawable(r, parser, attrs, theme); verifyRequiredAttributes(a); a.recycle(); updateLocalState(); } - private void verifyRequiredAttributes(TypedArray a) throws XmlPullParserException { - // If we're not waiting on a theme, verify required attributes. - if (getDrawable() == null && (mState.mThemeAttrs == null - || mState.mThemeAttrs[R.styleable.AnimatedRotateDrawable_drawable] == 0)) { - throw new XmlPullParserException(a.getPositionDescription() - + ": tag requires a 'drawable' attribute or " - + "child tag defining a drawable"); - } - } - @Override - void updateStateFromTypedArray(TypedArray a) { - super.updateStateFromTypedArray(a); + public void applyTheme(@NonNull Theme t) { + super.applyTheme(t); - final AnimatedRotateState state = mState; - - if (a.hasValue(R.styleable.AnimatedRotateDrawable_pivotX)) { - final TypedValue tv = a.peekValue(R.styleable.AnimatedRotateDrawable_pivotX); - state.mPivotXRel = tv.type == TypedValue.TYPE_FRACTION; - state.mPivotX = state.mPivotXRel ? tv.getFraction(1.0f, 1.0f) : tv.getFloat(); - } - - if (a.hasValue(R.styleable.AnimatedRotateDrawable_pivotY)) { - final TypedValue tv = a.peekValue(R.styleable.AnimatedRotateDrawable_pivotY); - state.mPivotYRel = tv.type == TypedValue.TYPE_FRACTION; - state.mPivotY = state.mPivotYRel ? tv.getFraction(1.0f, 1.0f) : tv.getFloat(); - } - - setFramesCount(a.getInt( - R.styleable.AnimatedRotateDrawable_framesCount, state.mFramesCount)); - setFramesDuration(a.getInt( - R.styleable.AnimatedRotateDrawable_frameDuration, state.mFrameDuration)); - - final Drawable dr = a.getDrawable(R.styleable.AnimatedRotateDrawable_drawable); - if (dr != null) { - setDrawable(dr); - } - } - - @Override - public void applyTheme(@Nullable Theme t) { final AnimatedRotateState state = mState; if (state == null) { return; @@ -195,13 +159,49 @@ public class AnimatedRotateDrawable extends DrawableWrapper implements Animatabl } } - // The drawable may have changed as a result of applying the theme, so - // apply the theme to the wrapped drawable last. - super.applyTheme(t); - updateLocalState(); } + private void verifyRequiredAttributes(@NonNull TypedArray a) throws XmlPullParserException { + // If we're not waiting on a theme, verify required attributes. + if (getDrawable() == null && (mState.mThemeAttrs == null + || mState.mThemeAttrs[R.styleable.AnimatedRotateDrawable_drawable] == 0)) { + throw new XmlPullParserException(a.getPositionDescription() + + ": tag requires a 'drawable' attribute or " + + "child tag defining a drawable"); + } + } + + private void updateStateFromTypedArray(@NonNull TypedArray a) { + final AnimatedRotateState state = mState; + if (state == null) { + return; + } + + // Account for any configuration changes. + state.mChangingConfigurations |= a.getChangingConfigurations(); + + // Extract the theme attributes, if any. + state.mThemeAttrs = a.extractThemeAttrs(); + + if (a.hasValue(R.styleable.AnimatedRotateDrawable_pivotX)) { + final TypedValue tv = a.peekValue(R.styleable.AnimatedRotateDrawable_pivotX); + state.mPivotXRel = tv.type == TypedValue.TYPE_FRACTION; + state.mPivotX = state.mPivotXRel ? tv.getFraction(1.0f, 1.0f) : tv.getFloat(); + } + + if (a.hasValue(R.styleable.AnimatedRotateDrawable_pivotY)) { + final TypedValue tv = a.peekValue(R.styleable.AnimatedRotateDrawable_pivotY); + state.mPivotYRel = tv.type == TypedValue.TYPE_FRACTION; + state.mPivotY = state.mPivotYRel ? tv.getFraction(1.0f, 1.0f) : tv.getFloat(); + } + + setFramesCount(a.getInt( + R.styleable.AnimatedRotateDrawable_framesCount, state.mFramesCount)); + setFramesDuration(a.getInt( + R.styleable.AnimatedRotateDrawable_frameDuration, state.mFrameDuration)); + } + public void setFramesCount(int framesCount) { mState.mFramesCount = framesCount; mIncrement = 360.0f / mState.mFramesCount; @@ -211,7 +211,15 @@ public class AnimatedRotateDrawable extends DrawableWrapper implements Animatabl mState.mFrameDuration = framesDuration; } + @Override + DrawableWrapperState mutateConstantState() { + mState = new AnimatedRotateState(mState, null); + return mState; + } + static final class AnimatedRotateState extends DrawableWrapper.DrawableWrapperState { + private int[] mThemeAttrs; + boolean mPivotXRel = false; float mPivotX = 0; boolean mPivotYRel = false; @@ -219,8 +227,8 @@ public class AnimatedRotateDrawable extends DrawableWrapper implements Animatabl int mFrameDuration = 150; int mFramesCount = 12; - public AnimatedRotateState(AnimatedRotateState orig) { - super(orig); + public AnimatedRotateState(AnimatedRotateState orig, Resources res) { + super(orig, res); if (orig != null) { mPivotXRel = orig.mPivotXRel; diff --git a/graphics/java/android/graphics/drawable/ClipDrawable.java b/graphics/java/android/graphics/drawable/ClipDrawable.java index 31fccd0880c9b..cdd336dded0ac 100644 --- a/graphics/java/android/graphics/drawable/ClipDrawable.java +++ b/graphics/java/android/graphics/drawable/ClipDrawable.java @@ -21,6 +21,8 @@ import com.android.internal.R; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.Resources.Theme; @@ -59,7 +61,7 @@ public class ClipDrawable extends DrawableWrapper { private ClipState mState; ClipDrawable() { - this(new ClipState(null), null); + this(new ClipState(null, null), null); } /** @@ -72,7 +74,7 @@ public class ClipDrawable extends DrawableWrapper { * {@link #VERTICAL} */ public ClipDrawable(Drawable drawable, int gravity, int orientation) { - this(new ClipState(null), null); + this(new ClipState(null, null), null); mState.mGravity = gravity; mState.mOrientation = orientation; @@ -81,45 +83,23 @@ public class ClipDrawable extends DrawableWrapper { } @Override - public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) + public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser, + @NonNull AttributeSet attrs, @Nullable Theme theme) throws XmlPullParserException, IOException { + final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.ClipDrawable); + + // Inflation will advance the XmlPullParser and AttributeSet. super.inflate(r, parser, attrs, theme); - final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.ClipDrawable); updateStateFromTypedArray(a); - inflateChildDrawable(r, parser, attrs, theme); verifyRequiredAttributes(a); a.recycle(); } - private void verifyRequiredAttributes(TypedArray a) throws XmlPullParserException { - // If we're not waiting on a theme, verify required attributes. - if (getDrawable() == null && (mState.mThemeAttrs == null - || mState.mThemeAttrs[R.styleable.ClipDrawable_drawable] == 0)) { - throw new XmlPullParserException(a.getPositionDescription() - + ": tag requires a 'drawable' attribute or " - + "child tag defining a drawable"); - } - } - @Override - void updateStateFromTypedArray(TypedArray a) { - super.updateStateFromTypedArray(a); + public void applyTheme(@NonNull Theme t) { + super.applyTheme(t); - final ClipState state = mState; - state.mOrientation = a.getInt( - R.styleable.ClipDrawable_clipOrientation, state.mOrientation); - state.mGravity = a.getInt( - R.styleable.ClipDrawable_gravity, state.mGravity); - - final Drawable dr = a.getDrawable(R.styleable.ClipDrawable_drawable); - if (dr != null) { - setDrawable(dr); - } - } - - @Override - public void applyTheme(Theme t) { final ClipState state = mState; if (state == null) { return; @@ -136,10 +116,34 @@ public class ClipDrawable extends DrawableWrapper { a.recycle(); } } + } - // The drawable may have changed as a result of applying the theme, so - // apply the theme to the wrapped drawable last. - super.applyTheme(t); + private void verifyRequiredAttributes(@NonNull TypedArray a) throws XmlPullParserException { + // If we're not waiting on a theme, verify required attributes. + if (getDrawable() == null && (mState.mThemeAttrs == null + || mState.mThemeAttrs[R.styleable.ClipDrawable_drawable] == 0)) { + throw new XmlPullParserException(a.getPositionDescription() + + ": tag requires a 'drawable' attribute or " + + "child tag defining a drawable"); + } + } + + private void updateStateFromTypedArray(@NonNull TypedArray a) { + final ClipState state = mState; + if (state == null) { + return; + } + + // Account for any configuration changes. + state.mChangingConfigurations |= a.getChangingConfigurations(); + + // Extract the theme attributes, if any. + state.mThemeAttrs = a.extractThemeAttrs(); + + state.mOrientation = a.getInt( + R.styleable.ClipDrawable_clipOrientation, state.mOrientation); + state.mGravity = a.getInt( + R.styleable.ClipDrawable_gravity, state.mGravity); } @Override @@ -200,12 +204,20 @@ public class ClipDrawable extends DrawableWrapper { } } + @Override + DrawableWrapperState mutateConstantState() { + mState = new ClipState(mState, null); + return mState; + } + static final class ClipState extends DrawableWrapper.DrawableWrapperState { + private int[] mThemeAttrs; + int mOrientation = HORIZONTAL; int mGravity = Gravity.LEFT; - ClipState(ClipState orig) { - super(orig); + ClipState(ClipState orig, Resources res) { + super(orig, res); if (orig != null) { mOrientation = orig.mOrientation; diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java index b95c183642ca2..ff287776e1a38 100644 --- a/graphics/java/android/graphics/drawable/Drawable.java +++ b/graphics/java/android/graphics/drawable/Drawable.java @@ -55,6 +55,8 @@ import java.lang.ref.WeakReference; import java.util.Arrays; import java.util.Collection; +import com.android.internal.R; + /** * A Drawable is a general abstraction for "something that can be drawn." Most * often you will deal with Drawable as the type of resource retrieved for @@ -791,8 +793,10 @@ public abstract class Drawable { /** * Applies the specified theme to this Drawable and its children. + * + * @param t the theme to apply */ - public void applyTheme(@SuppressWarnings("unused") Theme t) { + public void applyTheme(@NonNull @SuppressWarnings("unused") Theme t) { } public boolean canApplyTheme() { @@ -1177,8 +1181,8 @@ public abstract class Drawable { * * @see #inflate(Resources, XmlPullParser, AttributeSet, Theme) */ - public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs) - throws XmlPullParserException, IOException { + public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser, + @NonNull AttributeSet attrs) throws XmlPullParserException, IOException { inflate(r, parser, attrs, null); } @@ -1192,17 +1196,11 @@ public abstract class Drawable { * @throws XmlPullParserException * @throws IOException */ - public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) + public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser, + @NonNull AttributeSet attrs, @Nullable 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); - } - - inflateWithAttributes(r, parser, a, com.android.internal.R.styleable.Drawable_visible); + final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.Drawable); + mVisible = a.getBoolean(R.styleable.Drawable_visible, mVisible); a.recycle(); } @@ -1212,8 +1210,8 @@ public abstract class Drawable { * @throws XmlPullParserException * @throws IOException */ - void inflateWithAttributes(Resources r, XmlPullParser parser, TypedArray attrs, int visibleAttr) - throws XmlPullParserException, IOException { + void inflateWithAttributes(@NonNull Resources r, @NonNull XmlPullParser parser, + @NonNull TypedArray attrs, int visibleAttr) throws XmlPullParserException, IOException { mVisible = attrs.getBoolean(visibleAttr, mVisible); } diff --git a/graphics/java/android/graphics/drawable/DrawableWrapper.java b/graphics/java/android/graphics/drawable/DrawableWrapper.java index 9185e1a064662..c42787000c49c 100644 --- a/graphics/java/android/graphics/drawable/DrawableWrapper.java +++ b/graphics/java/android/graphics/drawable/DrawableWrapper.java @@ -16,6 +16,8 @@ package android.graphics.drawable; +import com.android.internal.R; + import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -23,6 +25,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.res.ColorStateList; import android.content.res.Resources; +import android.content.res.Resources.Theme; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.Canvas; @@ -33,6 +36,7 @@ import android.graphics.PixelFormat; import android.graphics.PorterDuff; import android.graphics.Rect; import android.util.AttributeSet; +import android.util.DisplayMetrics; import android.view.View; import java.io.IOException; @@ -112,7 +116,66 @@ public abstract class DrawableWrapper extends Drawable implements Drawable.Callb return mDrawable; } - void updateStateFromTypedArray(TypedArray a) { + @Override + public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser, + @NonNull AttributeSet attrs, @Nullable Theme theme) + throws XmlPullParserException, IOException { + super.inflate(r, parser, attrs, theme); + + final DrawableWrapperState state = mState; + if (state == null) { + return; + } + + // The density may have changed since the last update. This will + // apply scaling to any existing constant state properties. + final int densityDpi = r.getDisplayMetrics().densityDpi; + final int targetDensity = densityDpi == 0 ? DisplayMetrics.DENSITY_DEFAULT : densityDpi; + state.setDensity(targetDensity); + + final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.DrawableWrapper); + updateStateFromTypedArray(a); + a.recycle(); + + inflateChildDrawable(r, parser, attrs, theme); + } + + @Override + public void applyTheme(@NonNull Theme t) { + super.applyTheme(t); + + // If we load the drawable later as part of updating from the typed + // array, it will already be themed correctly. So, we can theme the + // local drawable first. + if (mDrawable != null && mDrawable.canApplyTheme()) { + mDrawable.applyTheme(t); + } + + final DrawableWrapperState state = mState; + if (state == null) { + return; + } + + final int densityDpi = t.getResources().getDisplayMetrics().densityDpi; + final int density = densityDpi == 0 ? DisplayMetrics.DENSITY_DEFAULT : densityDpi; + state.setDensity(density); + + if (state.mThemeAttrs != null) { + final TypedArray a = t.resolveAttributes( + state.mThemeAttrs, R.styleable.DrawableWrapper); + updateStateFromTypedArray(a); + a.recycle(); + } + } + + /** + * Updates constant state properties from the provided typed array. + *

+ * Implementing subclasses should call through to the super method first. + * + * @param a the typed array rom which properties should be read + */ + private void updateStateFromTypedArray(@NonNull TypedArray a) { final DrawableWrapperState state = mState; if (state == null) { return; @@ -124,20 +187,8 @@ public abstract class DrawableWrapper extends Drawable implements Drawable.Callb // Extract the theme attributes, if any. state.mThemeAttrs = a.extractThemeAttrs(); - // TODO: Consider using R.styleable.DrawableWrapper_drawable - } - - @Override - public void applyTheme(Resources.Theme t) { - super.applyTheme(t); - - final DrawableWrapperState state = mState; - if (state == null) { - return; - } - - if (mDrawable != null && mDrawable.canApplyTheme()) { - mDrawable.applyTheme(t); + if (a.hasValueOrEmpty(R.styleable.DrawableWrapper_drawable)) { + setDrawable(a.getDrawable(R.styleable.DrawableWrapper_drawable)); } } @@ -371,8 +422,9 @@ public abstract class DrawableWrapper extends Drawable implements Drawable.Callb * child element will take precedence over any other child elements or * explicit drawable attribute. */ - void inflateChildDrawable(Resources r, XmlPullParser parser, AttributeSet attrs, - Resources.Theme theme) throws XmlPullParserException, IOException { + private void inflateChildDrawable(@NonNull Resources r, @NonNull XmlPullParser parser, + @NonNull AttributeSet attrs, @Nullable Theme theme) + throws XmlPullParserException, IOException { // Seek to the first child element. Drawable dr = null; int type; @@ -390,17 +442,61 @@ public abstract class DrawableWrapper extends Drawable implements Drawable.Callb } abstract static class DrawableWrapperState extends Drawable.ConstantState { - int[] mThemeAttrs; + private int[] mThemeAttrs; + int mChangingConfigurations; + int mDensity = DisplayMetrics.DENSITY_DEFAULT; Drawable.ConstantState mDrawableState; - DrawableWrapperState(DrawableWrapperState orig) { + DrawableWrapperState(@Nullable DrawableWrapperState orig, @Nullable Resources res) { if (orig != null) { mThemeAttrs = orig.mThemeAttrs; mChangingConfigurations = orig.mChangingConfigurations; mDrawableState = orig.mDrawableState; } + + final int density; + if (res != null) { + density = res.getDisplayMetrics().densityDpi; + } else if (orig != null) { + density = orig.mDensity; + } else { + density = 0; + } + + mDensity = density == 0 ? DisplayMetrics.DENSITY_DEFAULT : density; + } + + /** + * Sets the constant state density. + *

+ * If the density has been previously set, dispatches the change to + * subclasses so that density-dependent properties may be scaled as + * necessary. + * + * @param targetDensity the new constant state density + */ + public final void setDensity(int targetDensity) { + if (mDensity != targetDensity) { + final int sourceDensity = mDensity; + mDensity = targetDensity; + + onDensityChanged(sourceDensity, targetDensity); + } + } + + /** + * Called when the constant state density changes. + *

+ * Subclasses with density-dependent constant state properties should + * override this method and scale their properties as necessary. + * + * @param sourceDensity the previous constant state density + * @param targetDensity the new constant state density + */ + void onDensityChanged(int sourceDensity, int targetDensity) { + // Stub method. } @Override @@ -425,7 +521,7 @@ public abstract class DrawableWrapper extends Drawable implements Drawable.Callb } @Override - public abstract Drawable newDrawable(Resources res); + public abstract Drawable newDrawable(@Nullable Resources res); @Override public int getChangingConfigurations() { diff --git a/graphics/java/android/graphics/drawable/InsetDrawable.java b/graphics/java/android/graphics/drawable/InsetDrawable.java index e1ebdbb65a266..927b9c9d9f6c8 100644 --- a/graphics/java/android/graphics/drawable/InsetDrawable.java +++ b/graphics/java/android/graphics/drawable/InsetDrawable.java @@ -22,14 +22,17 @@ import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import android.annotation.NonNull; +import android.annotation.Nullable; import android.content.res.Resources; import android.content.res.Resources.Theme; import android.content.res.TypedArray; +import android.graphics.Bitmap; import android.graphics.Insets; import android.graphics.Outline; import android.graphics.PixelFormat; import android.graphics.Rect; import android.util.AttributeSet; +import android.util.DisplayMetrics; import java.io.IOException; @@ -58,7 +61,7 @@ public class InsetDrawable extends DrawableWrapper { * No-arg constructor used by drawable inflation. */ InsetDrawable() { - this(new InsetState(null), null); + this(new InsetState(null, null), null); } /** @@ -67,7 +70,7 @@ public class InsetDrawable extends DrawableWrapper { * @param drawable The drawable to inset. * @param inset Inset in pixels around the drawable. */ - public InsetDrawable(Drawable drawable, int inset) { + public InsetDrawable(@Nullable Drawable drawable, int inset) { this(drawable, inset, inset, inset, inset); } @@ -80,9 +83,9 @@ public class InsetDrawable extends DrawableWrapper { * @param insetRight Right inset in pixels. * @param insetBottom Bottom inset in pixels. */ - public InsetDrawable(Drawable drawable, int insetLeft, int insetTop,int insetRight, - int insetBottom) { - this(new InsetState(null), null); + public InsetDrawable(@Nullable Drawable drawable, int insetLeft, int insetTop, + int insetRight, int insetBottom) { + this(new InsetState(null, null), null); mState.mInsetLeft = insetLeft; mState.mInsetTop = insetTop; @@ -93,69 +96,23 @@ public class InsetDrawable extends DrawableWrapper { } @Override - public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) + public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser, + @NonNull AttributeSet attrs, @Nullable Theme theme) throws XmlPullParserException, IOException { + final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.InsetDrawable); + + // Inflation will advance the XmlPullParser and AttributeSet. super.inflate(r, parser, attrs, theme); - final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.InsetDrawable); updateStateFromTypedArray(a); - inflateChildDrawable(r, parser, attrs, theme); verifyRequiredAttributes(a); a.recycle(); } - private void verifyRequiredAttributes(TypedArray a) throws XmlPullParserException { - // If we're not waiting on a theme, verify required attributes. - if (getDrawable() == null && (mState.mThemeAttrs == null - || mState.mThemeAttrs[R.styleable.InsetDrawable_drawable] == 0)) { - throw new XmlPullParserException(a.getPositionDescription() - + ": tag requires a 'drawable' attribute or " - + "child tag defining a drawable"); - } - } - @Override - void updateStateFromTypedArray(TypedArray a) { - super.updateStateFromTypedArray(a); + public void applyTheme(@NonNull Theme t) { + super.applyTheme(t); - final InsetState state = mState; - final int N = a.getIndexCount(); - for (int i = 0; i < N; i++) { - final int attr = a.getIndex(i); - switch (attr) { - case R.styleable.InsetDrawable_drawable: - final Drawable dr = a.getDrawable(attr); - if (dr != null) { - setDrawable(dr); - } - break; - case R.styleable.InsetDrawable_inset: - final int inset = a.getDimensionPixelOffset(attr, Integer.MIN_VALUE); - if (inset != Integer.MIN_VALUE) { - state.mInsetLeft = inset; - state.mInsetTop = inset; - state.mInsetRight = inset; - state.mInsetBottom = inset; - } - break; - case R.styleable.InsetDrawable_insetLeft: - state.mInsetLeft = a.getDimensionPixelOffset(attr, state.mInsetLeft); - break; - case R.styleable.InsetDrawable_insetTop: - state.mInsetTop = a.getDimensionPixelOffset(attr, state.mInsetTop); - break; - case R.styleable.InsetDrawable_insetRight: - state.mInsetRight = a.getDimensionPixelOffset(attr, state.mInsetRight); - break; - case R.styleable.InsetDrawable_insetBottom: - state.mInsetBottom = a.getDimensionPixelOffset(attr, state.mInsetBottom); - break; - } - } - } - - @Override - public void applyTheme(Theme t) { final InsetState state = mState; if (state == null) { return; @@ -172,10 +129,47 @@ public class InsetDrawable extends DrawableWrapper { a.recycle(); } } + } - // The drawable may have changed as a result of applying the theme, so - // apply the theme to the wrapped drawable last. - super.applyTheme(t); + private void verifyRequiredAttributes(@NonNull TypedArray a) throws XmlPullParserException { + // If we're not waiting on a theme, verify required attributes. + if (getDrawable() == null && (mState.mThemeAttrs == null + || mState.mThemeAttrs[R.styleable.InsetDrawable_drawable] == 0)) { + throw new XmlPullParserException(a.getPositionDescription() + + ": tag requires a 'drawable' attribute or " + + "child tag defining a drawable"); + } + } + + private void updateStateFromTypedArray(@NonNull TypedArray a) { + final InsetState state = mState; + if (state == null) { + return; + } + + // Account for any configuration changes. + state.mChangingConfigurations |= a.getChangingConfigurations(); + + // Extract the theme attributes, if any. + state.mThemeAttrs = a.extractThemeAttrs(); + + // Inset attribute may be overridden by more specific attributes. + if (a.hasValue(R.styleable.InsetDrawable_inset)) { + final int inset = a.getDimensionPixelOffset(R.styleable.InsetDrawable_inset, 0); + state.mInsetLeft = inset; + state.mInsetTop = inset; + state.mInsetRight = inset; + state.mInsetBottom = inset; + } + + state.mInsetLeft = a.getDimensionPixelOffset( + R.styleable.InsetDrawable_insetLeft, state.mInsetLeft); + state.mInsetRight = a.getDimensionPixelOffset( + R.styleable.InsetDrawable_insetRight, state.mInsetRight); + state.mInsetTop = a.getDimensionPixelOffset( + R.styleable.InsetDrawable_insetTop, state.mInsetTop); + state.mInsetBottom = a.getDimensionPixelOffset( + R.styleable.InsetDrawable_insetBottom, state.mInsetBottom); } @Override @@ -243,30 +237,72 @@ public class InsetDrawable extends DrawableWrapper { @Override DrawableWrapperState mutateConstantState() { - mState = new InsetState(mState); + mState = new InsetState(mState, null); return mState; } static final class InsetState extends DrawableWrapper.DrawableWrapperState { + private int[] mThemeAttrs; + int mInsetLeft = 0; int mInsetTop = 0; int mInsetRight = 0; int mInsetBottom = 0; - InsetState(InsetState orig) { - super(orig); + InsetState(@Nullable InsetState orig, @Nullable Resources res) { + super(orig, res); if (orig != null) { mInsetLeft = orig.mInsetLeft; mInsetTop = orig.mInsetTop; mInsetRight = orig.mInsetRight; mInsetBottom = orig.mInsetBottom; + + if (orig.mDensity != mDensity) { + applyDensityScaling(orig.mDensity, mDensity); + } } } @Override - public Drawable newDrawable(Resources res) { - return new InsetDrawable(this, res); + void onDensityChanged(int sourceDensity, int targetDensity) { + super.onDensityChanged(sourceDensity, targetDensity); + + applyDensityScaling(sourceDensity, targetDensity); + } + + /** + * Called when the constant state density changes to scale + * density-dependent properties specific to insets. + * + * @param sourceDensity the previous constant state density + * @param targetDensity the new constant state density + */ + private void applyDensityScaling(int sourceDensity, int targetDensity) { + mInsetLeft = Bitmap.scaleFromDensity(mInsetLeft, sourceDensity, targetDensity); + mInsetTop = Bitmap.scaleFromDensity(mInsetTop, sourceDensity, targetDensity); + mInsetRight = Bitmap.scaleFromDensity(mInsetRight, sourceDensity, targetDensity); + mInsetBottom = Bitmap.scaleFromDensity(mInsetBottom, sourceDensity, targetDensity); + } + + @Override + public Drawable newDrawable(@Nullable Resources res) { + // If this drawable is being created for a different density, + // just create a new constant state and call it a day. + final InsetState state; + if (res != null) { + final int densityDpi = res.getDisplayMetrics().densityDpi; + final int density = densityDpi == 0 ? DisplayMetrics.DENSITY_DEFAULT : densityDpi; + if (density != mDensity) { + state = new InsetState(this, res); + } else { + state = this; + } + } else { + state = this; + } + + return new InsetDrawable(state, res); } } @@ -274,7 +310,7 @@ public class InsetDrawable extends DrawableWrapper { * The one constructor to rule them all. This is called by all public * constructors to set the state and initialize local properties. */ - private InsetDrawable(InsetState state, Resources res) { + private InsetDrawable(@NonNull InsetState state, @Nullable Resources res) { super(state, res); mState = state; diff --git a/graphics/java/android/graphics/drawable/RotateDrawable.java b/graphics/java/android/graphics/drawable/RotateDrawable.java index 036a078eb00d5..1531ba2380fb8 100644 --- a/graphics/java/android/graphics/drawable/RotateDrawable.java +++ b/graphics/java/android/graphics/drawable/RotateDrawable.java @@ -21,6 +21,8 @@ import com.android.internal.R; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.graphics.Canvas; import android.graphics.Rect; import android.content.res.Resources; @@ -58,22 +60,46 @@ public class RotateDrawable extends DrawableWrapper { * Creates a new rotating drawable with no wrapped drawable. */ public RotateDrawable() { - this(new RotateState(null), null); + this(new RotateState(null, null), null); } @Override - public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) + public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser, + @NonNull AttributeSet attrs, @Nullable Theme theme) throws XmlPullParserException, IOException { final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.RotateDrawable); - super.inflateWithAttributes(r, parser, a, R.styleable.RotateDrawable_visible); + + // Inflation will advance the XmlPullParser and AttributeSet. + super.inflate(r, parser, attrs, theme); updateStateFromTypedArray(a); - inflateChildDrawable(r, parser, attrs, theme); verifyRequiredAttributes(a); a.recycle(); } - private void verifyRequiredAttributes(TypedArray a) throws XmlPullParserException { + @Override + public void applyTheme(@NonNull Theme t) { + super.applyTheme(t); + + final RotateState state = mState; + if (state == null) { + return; + } + + if (state.mThemeAttrs != null) { + final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.RotateDrawable); + try { + updateStateFromTypedArray(a); + verifyRequiredAttributes(a); + } catch (XmlPullParserException e) { + throw new RuntimeException(e); + } finally { + a.recycle(); + } + } + } + + private void verifyRequiredAttributes(@NonNull TypedArray a) throws XmlPullParserException { // If we're not waiting on a theme, verify required attributes. if (getDrawable() == null && (mState.mThemeAttrs == null || mState.mThemeAttrs[R.styleable.RotateDrawable_drawable] == 0)) { @@ -83,11 +109,14 @@ public class RotateDrawable extends DrawableWrapper { } } - @Override - void updateStateFromTypedArray(TypedArray a) { - super.updateStateFromTypedArray(a); - + private void updateStateFromTypedArray(@NonNull TypedArray a) { final RotateState state = mState; + if (state == null) { + return; + } + + // Account for any configuration changes. + state.mChangingConfigurations |= a.getChangingConfigurations(); // Extract the theme attributes, if any. state.mThemeAttrs = a.extractThemeAttrs(); @@ -109,35 +138,6 @@ public class RotateDrawable extends DrawableWrapper { state.mToDegrees = a.getFloat( R.styleable.RotateDrawable_toDegrees, state.mToDegrees); state.mCurrentDegrees = state.mFromDegrees; - - final Drawable dr = a.getDrawable(R.styleable.RotateDrawable_drawable); - if (dr != null) { - setDrawable(dr); - } - } - - @Override - public void applyTheme(Theme t) { - final RotateState state = mState; - if (state == null) { - return; - } - - if (state.mThemeAttrs != null) { - final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.RotateDrawable); - try { - updateStateFromTypedArray(a); - verifyRequiredAttributes(a); - } catch (XmlPullParserException e) { - throw new RuntimeException(e); - } finally { - a.recycle(); - } - } - - // The drawable may have changed as a result of applying the theme, so - // apply the theme to the wrapped drawable last. - super.applyTheme(t); } @Override @@ -316,11 +316,13 @@ public class RotateDrawable extends DrawableWrapper { @Override DrawableWrapperState mutateConstantState() { - mState = new RotateState(mState); + mState = new RotateState(mState, null); return mState; } static final class RotateState extends DrawableWrapper.DrawableWrapperState { + private int[] mThemeAttrs; + boolean mPivotXRel = true; float mPivotX = 0.5f; boolean mPivotYRel = true; @@ -329,8 +331,8 @@ public class RotateDrawable extends DrawableWrapper { float mToDegrees = 360.0f; float mCurrentDegrees = 0.0f; - RotateState(RotateState orig) { - super(orig); + RotateState(RotateState orig, Resources res) { + super(orig, res); if (orig != null) { mPivotXRel = orig.mPivotXRel; diff --git a/graphics/java/android/graphics/drawable/ScaleDrawable.java b/graphics/java/android/graphics/drawable/ScaleDrawable.java index f9206b7d3c26a..f87c19a40538f 100644 --- a/graphics/java/android/graphics/drawable/ScaleDrawable.java +++ b/graphics/java/android/graphics/drawable/ScaleDrawable.java @@ -21,6 +21,8 @@ import com.android.internal.R; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.content.res.Resources; import android.content.res.Resources.Theme; import android.content.res.TypedArray; @@ -67,7 +69,7 @@ public class ScaleDrawable extends DrawableWrapper { private ScaleState mState; ScaleDrawable() { - this(new ScaleState(null), null); + this(new ScaleState(null, null), null); } /** @@ -83,7 +85,7 @@ public class ScaleDrawable extends DrawableWrapper { * is at the maximum value, or -1 to not scale height */ public ScaleDrawable(Drawable drawable, int gravity, float scaleWidth, float scaleHeight) { - this(new ScaleState(null), null); + this(new ScaleState(null, null), null); mState.mGravity = gravity; mState.mScaleWidth = scaleWidth; @@ -93,20 +95,46 @@ public class ScaleDrawable extends DrawableWrapper { } @Override - public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) + public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser, + @NonNull AttributeSet attrs, @Nullable Theme theme) throws XmlPullParserException, IOException { + final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.ScaleDrawable); + + // Inflation will advance the XmlPullParser and AttributeSet. super.inflate(r, parser, attrs, theme); - final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.ScaleDrawable); updateStateFromTypedArray(a); - inflateChildDrawable(r, parser, attrs, theme); verifyRequiredAttributes(a); a.recycle(); updateLocalState(); } - private void verifyRequiredAttributes(TypedArray a) throws XmlPullParserException { + @Override + public void applyTheme(@NonNull Theme t) { + super.applyTheme(t); + + final ScaleState state = mState; + if (state == null) { + return; + } + + if (state.mThemeAttrs != null) { + final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.ScaleDrawable); + try { + updateStateFromTypedArray(a); + verifyRequiredAttributes(a); + } catch (XmlPullParserException e) { + throw new RuntimeException(e); + } finally { + a.recycle(); + } + } + + updateLocalState(); + } + + private void verifyRequiredAttributes(@NonNull TypedArray a) throws XmlPullParserException { // If we're not waiting on a theme, verify required attributes. if (getDrawable() == null && (mState.mThemeAttrs == null || mState.mThemeAttrs[R.styleable.ScaleDrawable_drawable] == 0)) { @@ -116,11 +144,18 @@ public class ScaleDrawable extends DrawableWrapper { } } - @Override - void updateStateFromTypedArray(TypedArray a) { - super.updateStateFromTypedArray(a); - + private void updateStateFromTypedArray(@NonNull TypedArray a) { final ScaleState state = mState; + if (state == null) { + return; + } + + // Account for any configuration changes. + state.mChangingConfigurations |= a.getChangingConfigurations(); + + // Extract the theme attributes, if any. + state.mThemeAttrs = a.extractThemeAttrs(); + state.mScaleWidth = getPercent(a, R.styleable.ScaleDrawable_scaleWidth, state.mScaleWidth); state.mScaleHeight = getPercent(a, @@ -131,11 +166,6 @@ public class ScaleDrawable extends DrawableWrapper { R.styleable.ScaleDrawable_useIntrinsicSizeAsMinimum, state.mUseIntrinsicSizeAsMin); state.mInitialLevel = a.getInt( R.styleable.ScaleDrawable_level, state.mInitialLevel); - - final Drawable dr = a.getDrawable(R.styleable.ScaleDrawable_drawable); - if (dr != null) { - setDrawable(dr); - } } private static float getPercent(TypedArray a, int index, float defaultValue) { @@ -156,33 +186,6 @@ public class ScaleDrawable extends DrawableWrapper { return defaultValue; } - @Override - public void applyTheme(Theme t) { - final ScaleState state = mState; - if (state == null) { - return; - } - - if (state.mThemeAttrs != null) { - final TypedArray a = t.resolveAttributes( - state.mThemeAttrs, R.styleable.ScaleDrawable); - try { - updateStateFromTypedArray(a); - verifyRequiredAttributes(a); - } catch (XmlPullParserException e) { - throw new RuntimeException(e); - } finally { - a.recycle(); - } - } - - // The drawable may have changed as a result of applying the theme, so - // apply the theme to the wrapped drawable last. - super.applyTheme(t); - - updateLocalState(); - } - @Override public void draw(Canvas canvas) { final Drawable d = getDrawable(); @@ -243,7 +246,7 @@ public class ScaleDrawable extends DrawableWrapper { @Override DrawableWrapperState mutateConstantState() { - mState = new ScaleState(mState); + mState = new ScaleState(mState, null); return mState; } @@ -251,14 +254,16 @@ public class ScaleDrawable extends DrawableWrapper { /** Constant used to disable scaling for a particular dimension. */ private static final float DO_NOT_SCALE = -1.0f; + private int[] mThemeAttrs; + float mScaleWidth = DO_NOT_SCALE; float mScaleHeight = DO_NOT_SCALE; int mGravity = Gravity.LEFT; boolean mUseIntrinsicSizeAsMin = false; int mInitialLevel = 0; - ScaleState(ScaleState orig) { - super(orig); + ScaleState(ScaleState orig, Resources res) { + super(orig, res); if (orig != null) { mScaleWidth = orig.mScaleWidth;