summaryrefslogtreecommitdiffstats
path: root/graphics/java
diff options
context:
space:
mode:
Diffstat (limited to 'graphics/java')
-rw-r--r--graphics/java/android/graphics/Bitmap.java12
-rw-r--r--graphics/java/android/graphics/Canvas.java122
-rw-r--r--graphics/java/android/graphics/ColorMatrix.java94
-rw-r--r--graphics/java/android/graphics/Outline.java11
-rw-r--r--graphics/java/android/graphics/Path.java4
-rw-r--r--graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java157
-rw-r--r--graphics/java/android/graphics/drawable/AnimatedStateListDrawable.java291
-rw-r--r--graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java102
-rw-r--r--graphics/java/android/graphics/drawable/AnimationDrawable.java99
-rw-r--r--graphics/java/android/graphics/drawable/BitmapDrawable.java49
-rw-r--r--graphics/java/android/graphics/drawable/ClipDrawable.java189
-rw-r--r--graphics/java/android/graphics/drawable/ColorDrawable.java32
-rw-r--r--graphics/java/android/graphics/drawable/Drawable.java139
-rw-r--r--graphics/java/android/graphics/drawable/DrawableContainer.java184
-rw-r--r--graphics/java/android/graphics/drawable/GradientDrawable.java149
-rw-r--r--graphics/java/android/graphics/drawable/InsetDrawable.java202
-rw-r--r--graphics/java/android/graphics/drawable/LayerDrawable.java95
-rw-r--r--graphics/java/android/graphics/drawable/LevelListDrawable.java37
-rw-r--r--graphics/java/android/graphics/drawable/NinePatchDrawable.java50
-rw-r--r--graphics/java/android/graphics/drawable/Ripple.java117
-rw-r--r--graphics/java/android/graphics/drawable/RippleBackground.java148
-rw-r--r--graphics/java/android/graphics/drawable/RippleDrawable.java426
-rw-r--r--graphics/java/android/graphics/drawable/RotateDrawable.java200
-rw-r--r--graphics/java/android/graphics/drawable/ScaleDrawable.java218
-rw-r--r--graphics/java/android/graphics/drawable/ShapeDrawable.java36
-rw-r--r--graphics/java/android/graphics/drawable/StateListDrawable.java178
-rw-r--r--graphics/java/android/graphics/drawable/TransitionDrawable.java19
-rw-r--r--graphics/java/android/graphics/drawable/VectorDrawable.java35
-rw-r--r--graphics/java/android/graphics/pdf/PdfEditor.java159
-rw-r--r--graphics/java/android/graphics/pdf/PdfRenderer.java4
30 files changed, 2298 insertions, 1260 deletions
diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index 3090ffd..72f6118 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -48,13 +48,8 @@ public final class Bitmap implements Parcelable {
/**
* Backing buffer for the Bitmap.
- * Made public for quick access from drawing methods -- do NOT modify
- * from outside this class
- *
- * @hide
*/
- @SuppressWarnings("UnusedDeclaration") // native code only
- public byte[] mBuffer;
+ private byte[] mBuffer;
@SuppressWarnings({"FieldCanBeLocal", "UnusedDeclaration"}) // Keep to finalize native resources
private final BitmapFinalizer mFinalizer;
@@ -309,7 +304,7 @@ public final class Bitmap implements Parcelable {
* there are no more references to this bitmap.
*/
public void recycle() {
- if (!mRecycled) {
+ if (!mRecycled && mFinalizer.mNativeBitmap != 0) {
if (nativeRecycle(mNativeBitmap)) {
// return value indicates whether native pixel object was actually recycled.
// false indicates that it is still in use at the native level and these
@@ -1576,7 +1571,7 @@ public final class Bitmap implements Parcelable {
}
private static class BitmapFinalizer {
- private final long mNativeBitmap;
+ private long mNativeBitmap;
// Native memory allocated for the duration of the Bitmap,
// if pixel data allocated into native memory, instead of java byte[]
@@ -1602,6 +1597,7 @@ public final class Bitmap implements Parcelable {
VMRuntime.getRuntime().registerNativeFree(mNativeAllocationByteCount);
}
nativeDestructor(mNativeBitmap);
+ mNativeBitmap = 0;
}
}
}
diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java
index f45c0cb..b0580d5 100644
--- a/graphics/java/android/graphics/Canvas.java
+++ b/graphics/java/android/graphics/Canvas.java
@@ -353,25 +353,51 @@ public class Canvas {
@Retention(RetentionPolicy.SOURCE)
public @interface Saveflags {}
- /** restore the current matrix when restore() is called */
+ /**
+ * Restore the current matrix when restore() is called.
+ */
public static final int MATRIX_SAVE_FLAG = 0x01;
- /** restore the current clip when restore() is called */
+
+ /**
+ * Restore the current clip when restore() is called.
+ */
public static final int CLIP_SAVE_FLAG = 0x02;
- /** the layer needs to per-pixel alpha */
+
+ /**
+ * The layer requires a per-pixel alpha channel.
+ */
public static final int HAS_ALPHA_LAYER_SAVE_FLAG = 0x04;
- /** the layer needs to 8-bits per color component */
+
+ /**
+ * The layer requires full 8-bit precision for each color channel.
+ */
public static final int FULL_COLOR_LAYER_SAVE_FLAG = 0x08;
- /** clip against the layer's bounds */
+
+ /**
+ * Clip drawing to the bounds of the offscreen layer, omit at your own peril.
+ * <p class="note"><strong>Note:</strong> it is strongly recommended to not
+ * omit this flag for any call to <code>saveLayer()</code> and
+ * <code>saveLayerAlpha()</code> variants. Not passing this flag generally
+ * triggers extremely poor performance with hardware accelerated rendering.
+ */
public static final int CLIP_TO_LAYER_SAVE_FLAG = 0x10;
- /** restore everything when restore() is called */
+
+ /**
+ * Restore everything when restore() is called (standard save flags).
+ * <p class="note"><strong>Note:</strong> for performance reasons, it is
+ * strongly recommended to pass this - the complete set of flags - to any
+ * call to <code>saveLayer()</code> and <code>saveLayerAlpha()</code>
+ * variants.
+ */
public static final int ALL_SAVE_FLAG = 0x1F;
/**
- * Saves the current matrix and clip onto a private stack. Subsequent
- * calls to translate,scale,rotate,skew,concat or clipRect,clipPath
- * will all operate as usual, but when the balancing call to restore()
- * is made, those calls will be forgotten, and the settings that existed
- * before the save() will be reinstated.
+ * Saves the current matrix and clip onto a private stack.
+ * <p>
+ * Subsequent calls to translate,scale,rotate,skew,concat or clipRect,
+ * clipPath will all operate as usual, but when the balancing call to
+ * restore() is made, those calls will be forgotten, and the settings that
+ * existed before the save() will be reinstated.
*
* @return The value to pass to restoreToCount() to balance this save()
*/
@@ -381,10 +407,15 @@ public class Canvas {
/**
* Based on saveFlags, can save the current matrix and clip onto a private
- * stack. Subsequent calls to translate,scale,rotate,skew,concat or
- * clipRect,clipPath will all operate as usual, but when the balancing
- * call to restore() is made, those calls will be forgotten, and the
- * settings that existed before the save() will be reinstated.
+ * stack.
+ * <p class="note"><strong>Note:</strong> if possible, use the
+ * parameter-less save(). It is simpler and faster than individually
+ * disabling the saving of matrix or clip with this method.
+ * <p>
+ * Subsequent calls to translate,scale,rotate,skew,concat or clipRect,
+ * clipPath will all operate as usual, but when the balancing call to
+ * restore() is made, those calls will be forgotten, and the settings that
+ * existed before the save() will be reinstated.
*
* @param saveFlags flag bits that specify which parts of the Canvas state
* to save/restore
@@ -395,19 +426,33 @@ public class Canvas {
}
/**
- * This behaves the same as save(), but in addition it allocates an
- * offscreen bitmap. All drawing calls are directed there, and only when
- * the balancing call to restore() is made is that offscreen transfered to
- * the canvas (or the previous layer). Subsequent calls to translate,
- * scale, rotate, skew, concat or clipRect, clipPath all operate on this
- * copy. When the balancing call to restore() is made, this copy is
- * deleted and the previous matrix/clip state is restored.
+ * This behaves the same as save(), but in addition it allocates and
+ * redirects drawing to an offscreen bitmap.
+ * <p class="note"><strong>Note:</strong> this method is very expensive,
+ * incurring more than double rendering cost for contained content. Avoid
+ * using this method, especially if the bounds provided are large, or if
+ * the {@link #CLIP_TO_LAYER_SAVE_FLAG} is omitted from the
+ * {@code saveFlags} parameter. It is recommended to use a
+ * {@link android.view.View#LAYER_TYPE_HARDWARE hardware layer} on a View
+ * to apply an xfermode, color filter, or alpha, as it will perform much
+ * better than this method.
+ * <p>
+ * All drawing calls are directed to a newly allocated offscreen bitmap.
+ * Only when the balancing call to restore() is made, is that offscreen
+ * buffer drawn back to the current target of the Canvas (either the
+ * screen, it's target Bitmap, or the previous layer).
+ * <p>
+ * Attributes of the Paint - {@link Paint#getAlpha() alpha},
+ * {@link Paint#getXfermode() Xfermode}, and
+ * {@link Paint#getColorFilter() ColorFilter} are applied when the
+ * offscreen bitmap is drawn back when restore() is called.
*
* @param bounds May be null. The maximum size the offscreen bitmap
* needs to be (in local coordinates)
* @param paint This is copied, and is applied to the offscreen when
* restore() is called.
- * @param saveFlags see _SAVE_FLAG constants
+ * @param saveFlags see _SAVE_FLAG constants, generally {@link #ALL_SAVE_FLAG} is recommended
+ * for performance reasons.
* @return value to pass to restoreToCount() to balance this save()
*/
public int saveLayer(@Nullable RectF bounds, @Nullable Paint paint, @Saveflags int saveFlags) {
@@ -442,19 +487,31 @@ public class Canvas {
}
/**
- * This behaves the same as save(), but in addition it allocates an
- * offscreen bitmap. All drawing calls are directed there, and only when
- * the balancing call to restore() is made is that offscreen transfered to
- * the canvas (or the previous layer). Subsequent calls to translate,
- * scale, rotate, skew, concat or clipRect, clipPath all operate on this
- * copy. When the balancing call to restore() is made, this copy is
- * deleted and the previous matrix/clip state is restored.
+ * This behaves the same as save(), but in addition it allocates and
+ * redirects drawing to an offscreen bitmap.
+ * <p class="note"><strong>Note:</strong> this method is very expensive,
+ * incurring more than double rendering cost for contained content. Avoid
+ * using this method, especially if the bounds provided are large, or if
+ * the {@link #CLIP_TO_LAYER_SAVE_FLAG} is omitted from the
+ * {@code saveFlags} parameter. It is recommended to use a
+ * {@link android.view.View#LAYER_TYPE_HARDWARE hardware layer} on a View
+ * to apply an xfermode, color filter, or alpha, as it will perform much
+ * better than this method.
+ * <p>
+ * All drawing calls are directed to a newly allocated offscreen bitmap.
+ * Only when the balancing call to restore() is made, is that offscreen
+ * buffer drawn back to the current target of the Canvas (either the
+ * screen, it's target Bitmap, or the previous layer).
+ * <p>
+ * The {@code alpha} parameter is applied when the offscreen bitmap is
+ * drawn back when restore() is called.
*
* @param bounds The maximum size the offscreen bitmap needs to be
* (in local coordinates)
* @param alpha The alpha to apply to the offscreen when when it is
drawn during restore()
- * @param saveFlags see _SAVE_FLAG constants
+ * @param saveFlags see _SAVE_FLAG constants, generally {@link #ALL_SAVE_FLAG} is recommended
+ * for performance reasons.
* @return value to pass to restoreToCount() to balance this call
*/
public int saveLayerAlpha(@Nullable RectF bounds, int alpha, @Saveflags int saveFlags) {
@@ -1644,6 +1701,9 @@ public class Canvas {
*/
public void drawText(@NonNull CharSequence text, int start, int end, float x, float y,
@NonNull Paint paint) {
+ if ((start | end | (end - start) | (text.length() - end)) < 0) {
+ throw new IndexOutOfBoundsException();
+ }
if (text instanceof String || text instanceof SpannedString ||
text instanceof SpannableString) {
native_drawText(mNativeCanvasWrapper, text.toString(), start, end, x, y,
diff --git a/graphics/java/android/graphics/ColorMatrix.java b/graphics/java/android/graphics/ColorMatrix.java
index 1242eb5..64f0c05 100644
--- a/graphics/java/android/graphics/ColorMatrix.java
+++ b/graphics/java/android/graphics/ColorMatrix.java
@@ -21,23 +21,43 @@ import android.util.FloatMath;
import java.util.Arrays;
/**
- * 4x5 matrix for transforming the color+alpha components of a Bitmap.
- * The matrix is stored in a single array, and its treated as follows:
+ * 4x5 matrix for transforming the color and alpha components of a Bitmap.
+ * The matrix can be passed as single array, and is treated as follows:
+ *
* <pre>
* [ a, b, c, d, e,
* f, g, h, i, j,
* k, l, m, n, o,
- * p, q, r, s, t ]
- * </pre>
+ * p, q, r, s, t ]</pre>
+ *
+ * <p>
+ * When applied to a color <code>[R, G, B, A]</code>, the resulting color
+ * is computed as:
+ * </p>
+ *
+ * <pre>
+ * R&rsquo; = a*R + b*G + c*B + d*A + e;
+ * G&rsquo; = f*R + g*G + h*B + i*A + j;
+ * B&rsquo; = k*R + l*G + m*B + n*A + o;
+ * A&rsquo; = p*R + q*G + r*B + s*A + t;</pre>
+ *
+ * <p>
+ * That resulting color <code>[R&rsquo;, G&rsquo;, B&rsquo;, A&rsquo;]</code>
+ * then has each channel clamped to the <code>0</code> to <code>255</code>
+ * range.
+ * </p>
+ *
+ * <p>
+ * The sample ColorMatrix below inverts incoming colors by scaling each
+ * channel by <code>-1</code>, and then shifting the result up by
+ * <code>255</code> to remain in the standard color space.
+ * </p>
*
- * When applied to a color <code>[r, g, b, a]</code>, the resulting color
- * is computed as (after clamping):
* <pre>
- * R' = a*R + b*G + c*B + d*A + e;
- * G' = f*R + g*G + h*B + i*A + j;
- * B' = k*R + l*G + m*B + n*A + o;
- * A' = p*R + q*G + r*B + s*A + t;
- * </pre>
+ * [ -1, 0, 0, 0, 255,
+ * 0, -1, 0, 0, 255,
+ * 0, 0, -1, 0, 255,
+ * 0, 0, 0, 1, 0 ]</pre>
*/
@SuppressWarnings({ "MismatchedReadAndWriteOfArray", "PointlessArithmeticExpression" })
public class ColorMatrix {
@@ -52,24 +72,24 @@ public class ColorMatrix {
}
/**
- * Create a new colormatrix initialized with the specified array of values.
+ * Create a new colormatrix initialized with the specified array of values.
*/
public ColorMatrix(float[] src) {
System.arraycopy(src, 0, mArray, 0, 20);
}
-
+
/**
* Create a new colormatrix initialized with the specified colormatrix.
*/
public ColorMatrix(ColorMatrix src) {
System.arraycopy(src.mArray, 0, mArray, 0, 20);
}
-
+
/**
* Return the array of floats representing this colormatrix.
*/
public final float[] getArray() { return mArray; }
-
+
/**
* Set this colormatrix to identity:
* <pre>
@@ -84,7 +104,7 @@ public class ColorMatrix {
Arrays.fill(a, 0);
a[0] = a[6] = a[12] = a[18] = 1;
}
-
+
/**
* Assign the src colormatrix into this matrix, copying all of its values.
*/
@@ -98,7 +118,7 @@ public class ColorMatrix {
public void set(float[] src) {
System.arraycopy(src, 0, mArray, 0, 20);
}
-
+
/**
* Set this colormatrix to scale by the specified values.
*/
@@ -114,12 +134,14 @@ public class ColorMatrix {
a[12] = bScale;
a[18] = aScale;
}
-
+
/**
* Set the rotation on a color axis by the specified values.
+ * <p>
* <code>axis=0</code> correspond to a rotation around the RED color
* <code>axis=1</code> correspond to a rotation around the GREEN color
* <code>axis=2</code> correspond to a rotation around the BLUE color
+ * </p>
*/
public void setRotate(int axis, float degrees) {
reset();
@@ -153,8 +175,10 @@ public class ColorMatrix {
/**
* Set this colormatrix to the concatenation of the two specified
* colormatrices, such that the resulting colormatrix has the same effect
- * as applying matB and then applying matA. It is legal for either matA or
- * matB to be the same colormatrix as this.
+ * as applying matB and then applying matA.
+ * <p>
+ * It is legal for either matA or matB to be the same colormatrix as this.
+ * </p>
*/
public void setConcat(ColorMatrix matA, ColorMatrix matB) {
float[] tmp;
@@ -163,7 +187,7 @@ public class ColorMatrix {
} else {
tmp = mArray;
}
-
+
final float[] a = matA.mArray;
final float[] b = matB.mArray;
int index = 0;
@@ -176,38 +200,43 @@ public class ColorMatrix {
a[j + 2] * b[14] + a[j + 3] * b[19] +
a[j + 4];
}
-
+
if (tmp != mArray) {
System.arraycopy(tmp, 0, mArray, 0, 20);
}
}
/**
- * Concat this colormatrix with the specified prematrix. This is logically
- * the same as calling setConcat(this, prematrix);
+ * Concat this colormatrix with the specified prematrix.
+ * <p>
+ * This is logically the same as calling setConcat(this, prematrix);
+ * </p>
*/
public void preConcat(ColorMatrix prematrix) {
setConcat(this, prematrix);
}
/**
- * Concat this colormatrix with the specified postmatrix. This is logically
- * the same as calling setConcat(postmatrix, this);
+ * Concat this colormatrix with the specified postmatrix.
+ * <p>
+ * This is logically the same as calling setConcat(postmatrix, this);
+ * </p>
*/
public void postConcat(ColorMatrix postmatrix) {
setConcat(postmatrix, this);
}
///////////////////////////////////////////////////////////////////////////
-
+
/**
- * Set the matrix to affect the saturation of colors. A value of 0 maps the
- * color to gray-scale. 1 is identity.
+ * Set the matrix to affect the saturation of colors.
+ *
+ * @param sat A value of 0 maps the color to gray-scale. 1 is identity.
*/
public void setSaturation(float sat) {
reset();
float[] m = mArray;
-
+
final float invSat = 1 - sat;
final float R = 0.213f * invSat;
final float G = 0.715f * invSat;
@@ -217,7 +246,7 @@ public class ColorMatrix {
m[5] = R; m[6] = G + sat; m[7] = B;
m[10] = R; m[11] = G; m[12] = B + sat;
}
-
+
/**
* Set the matrix to convert RGB to YUV
*/
@@ -229,7 +258,7 @@ public class ColorMatrix {
m[5] = -0.16874f; m[6] = -0.33126f; m[7] = 0.5f;
m[10] = 0.5f; m[11] = -0.41869f; m[12] = -0.08131f;
}
-
+
/**
* Set the matrix to convert from YUV to RGB
*/
@@ -242,4 +271,3 @@ public class ColorMatrix {
m[10] = 1; m[11] = 1.772f; m[12] = 0;
}
}
-
diff --git a/graphics/java/android/graphics/Outline.java b/graphics/java/android/graphics/Outline.java
index 4bf0b71..f76184f 100644
--- a/graphics/java/android/graphics/Outline.java
+++ b/graphics/java/android/graphics/Outline.java
@@ -221,4 +221,15 @@ public final class Outline {
mRect = null;
mRadius = -1.0f;
}
+
+ /**
+ * Offsets the Outline by (dx,dy)
+ */
+ public void offset(int dx, int dy) {
+ if (mRect != null) {
+ mRect.offset(dx, dy);
+ } else if (mPath != null) {
+ mPath.offset(dx, dy);
+ }
+ }
}
diff --git a/graphics/java/android/graphics/Path.java b/graphics/java/android/graphics/Path.java
index c40a66d..0e9823d 100644
--- a/graphics/java/android/graphics/Path.java
+++ b/graphics/java/android/graphics/Path.java
@@ -678,7 +678,7 @@ public class Path {
}
/**
- * Offset the path by (dx,dy), returning true on success
+ * Offset the path by (dx,dy)
*
* @param dx The amount in the X direction to offset the entire path
* @param dy The amount in the Y direction to offset the entire path
@@ -695,7 +695,7 @@ public class Path {
}
/**
- * Offset the path by (dx,dy), returning true on success
+ * Offset the path by (dx,dy)
*
* @param dx The amount in the X direction to offset the entire path
* @param dy The amount in the Y direction to offset the entire path
diff --git a/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java b/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java
index 9fb3fb4..d2799e1 100644
--- a/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java
+++ b/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java
@@ -16,6 +16,8 @@
package android.graphics.drawable;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.ColorFilter;
@@ -41,6 +43,7 @@ import com.android.internal.R;
*/
public class AnimatedRotateDrawable extends Drawable implements Drawable.Callback, Runnable,
Animatable {
+ private static final String TAG = "AnimatedRotateDrawable";
private AnimatedRotateState mState;
private boolean mMutated;
@@ -186,6 +189,11 @@ public class AnimatedRotateDrawable extends Drawable implements Drawable.Callbac
}
@Override
+ public boolean canApplyTheme() {
+ return (mState != null && mState.canApplyTheme()) || super.canApplyTheme();
+ }
+
+ @Override
public void invalidateDrawable(Drawable who) {
final Callback callback = getCallback();
if (callback != null) {
@@ -254,62 +262,112 @@ public class AnimatedRotateDrawable extends Drawable implements Drawable.Callbac
}
@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.AnimatedRotateDrawable);
-
super.inflateWithAttributes(r, parser, a, R.styleable.AnimatedRotateDrawable_visible);
+ updateStateFromTypedArray(a);
+ a.recycle();
- TypedValue tv = a.peekValue(R.styleable.AnimatedRotateDrawable_pivotX);
- final boolean pivotXRel = tv.type == TypedValue.TYPE_FRACTION;
- final float pivotX = pivotXRel ? tv.getFraction(1.0f, 1.0f) : tv.getFloat();
+ inflateChildElements(r, parser, attrs, theme);
- tv = a.peekValue(R.styleable.AnimatedRotateDrawable_pivotY);
- final boolean pivotYRel = tv.type == TypedValue.TYPE_FRACTION;
- final float pivotY = pivotYRel ? tv.getFraction(1.0f, 1.0f) : tv.getFloat();
+ init();
+ }
- setFramesCount(a.getInt(R.styleable.AnimatedRotateDrawable_framesCount, 12));
- setFramesDuration(a.getInt(R.styleable.AnimatedRotateDrawable_frameDuration, 150));
+ @Override
+ public void applyTheme(@Nullable Theme t) {
+ super.applyTheme(t);
- final int res = a.getResourceId(R.styleable.AnimatedRotateDrawable_drawable, 0);
- Drawable drawable = null;
- if (res > 0) {
- drawable = r.getDrawable(res, theme);
+ final AnimatedRotateState state = mState;
+ if (state == null) {
+ return;
}
- a.recycle();
+ if (state.mThemeAttrs != null) {
+ final TypedArray a = t.resolveAttributes(
+ state.mThemeAttrs, R.styleable.AnimatedRotateDrawable);
+ try {
+ updateStateFromTypedArray(a);
+ verifyRequiredAttributes(a);
+ } catch (XmlPullParserException e) {
+ throw new RuntimeException(e);
+ } finally {
+ a.recycle();
+ }
+ }
+
+ if (state.mDrawable != null && state.mDrawable.canApplyTheme()) {
+ state.mDrawable.applyTheme(t);
+ }
+
+ init();
+ }
+ private void updateStateFromTypedArray(TypedArray a) {
+ final AnimatedRotateState state = mState;
+
+ // 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));
+
+ final Drawable dr = a.getDrawable(R.styleable.AnimatedRotateDrawable_drawable);
+ if (dr != null) {
+ state.mDrawable = dr;
+ dr.setCallback(this);
+ }
+ }
+
+ private void inflateChildElements(Resources r, XmlPullParser parser, AttributeSet attrs,
+ Theme theme) throws XmlPullParserException, IOException {
+ final AnimatedRotateState state = mState;
+
+ Drawable dr = null;
int outerDepth = parser.getDepth();
int type;
- while ((type = parser.next()) != XmlPullParser.END_DOCUMENT &&
- (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
-
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
if (type != XmlPullParser.START_TAG) {
continue;
}
- if ((drawable = Drawable.createFromXmlInner(r, parser, attrs, theme)) == null) {
- Log.w("drawable", "Bad element under <animated-rotate>: "
- + parser .getName());
+ if ((dr = Drawable.createFromXmlInner(r, parser, attrs, theme)) == null) {
+ Log.w(TAG, "Bad element under <animated-rotate>: " + parser.getName());
}
}
- if (drawable == null) {
- Log.w("drawable", "No drawable specified for <animated-rotate>");
+ if (dr != null) {
+ state.mDrawable = dr;
+ dr.setCallback(this);
}
+ }
- final AnimatedRotateState rotateState = mState;
- rotateState.mDrawable = drawable;
- rotateState.mPivotXRel = pivotXRel;
- rotateState.mPivotX = pivotX;
- rotateState.mPivotYRel = pivotYRel;
- rotateState.mPivotY = pivotY;
-
- init();
-
- if (drawable != null) {
- drawable.setCallback(this);
+ private void verifyRequiredAttributes(TypedArray a) throws XmlPullParserException {
+ // If we're not waiting on a theme, verify required attributes.
+ if (mState.mDrawable == null && (mState.mThemeAttrs == null
+ || mState.mThemeAttrs[R.styleable.AnimatedRotateDrawable_drawable] == 0)) {
+ throw new XmlPullParserException(a.getPositionDescription()
+ + ": <animated-rotate> tag requires a 'drawable' attribute or "
+ + "child tag defining a drawable");
}
}
@@ -331,17 +389,27 @@ public class AnimatedRotateDrawable extends Drawable implements Drawable.Callbac
return this;
}
+ /**
+ * @hide
+ */
+ public void clearMutated() {
+ super.clearMutated();
+ mState.mDrawable.clearMutated();
+ mMutated = false;
+ }
+
final static class AnimatedRotateState extends Drawable.ConstantState {
Drawable mDrawable;
+ int[] mThemeAttrs;
int mChangingConfigurations;
- boolean mPivotXRel;
- float mPivotX;
- boolean mPivotYRel;
- float mPivotY;
- int mFrameDuration;
- int mFramesCount;
+ boolean mPivotXRel = false;
+ float mPivotX = 0;
+ boolean mPivotYRel = false;
+ float mPivotY = 0;
+ int mFrameDuration = 150;
+ int mFramesCount = 12;
private boolean mCanConstantState;
private boolean mCheckedConstantState;
@@ -358,6 +426,7 @@ public class AnimatedRotateDrawable extends Drawable implements Drawable.Callbac
mDrawable.setLayoutDirection(orig.mDrawable.getLayoutDirection());
mDrawable.setBounds(orig.mDrawable.getBounds());
mDrawable.setLevel(orig.mDrawable.getLevel());
+ mThemeAttrs = orig.mThemeAttrs;
mPivotXRel = orig.mPivotXRel;
mPivotX = orig.mPivotX;
mPivotYRel = orig.mPivotYRel;
@@ -379,6 +448,12 @@ public class AnimatedRotateDrawable extends Drawable implements Drawable.Callbac
}
@Override
+ public boolean canApplyTheme() {
+ return mThemeAttrs != null || (mDrawable != null && mDrawable.canApplyTheme())
+ || super.canApplyTheme();
+ }
+
+ @Override
public int getChangingConfigurations() {
return mChangingConfigurations;
}
diff --git a/graphics/java/android/graphics/drawable/AnimatedStateListDrawable.java b/graphics/java/android/graphics/drawable/AnimatedStateListDrawable.java
index cb09bbf..4af5946 100644
--- a/graphics/java/android/graphics/drawable/AnimatedStateListDrawable.java
+++ b/graphics/java/android/graphics/drawable/AnimatedStateListDrawable.java
@@ -139,27 +139,21 @@ public class AnimatedStateListDrawable extends StateListDrawable {
@Override
protected boolean onStateChange(int[] stateSet) {
- final int keyframeIndex = mState.indexOfKeyframe(stateSet);
- if (keyframeIndex == getCurrentIndex()) {
- // Propagate state change to current keyframe.
- final Drawable current = getCurrent();
- if (current != null) {
- return current.setState(stateSet);
- }
- return false;
- }
-
- // Attempt to find a valid transition to the keyframe.
- if (selectTransition(keyframeIndex)) {
- return true;
- }
+ // If we're not already at the target index, either attempt to find a
+ // valid transition to it or jump directly there.
+ final int targetIndex = mState.indexOfKeyframe(stateSet);
+ boolean changed = targetIndex != getCurrentIndex()
+ && (selectTransition(targetIndex) || selectDrawable(targetIndex));
- // No valid transition, attempt to jump directly to the keyframe.
- if (selectDrawable(keyframeIndex)) {
- return true;
+ // We need to propagate the state change to the current drawable, but
+ // we can't call StateListDrawable.onStateChange() without changing the
+ // current drawable.
+ final Drawable current = getCurrent();
+ if (current != null) {
+ changed |= current.setState(stateSet);
}
- return super.onStateChange(stateSet);
+ return changed;
}
private boolean selectTransition(int toIndex) {
@@ -205,6 +199,8 @@ public class AnimatedStateListDrawable extends StateListDrawable {
return false;
}
+ boolean hasReversibleFlag = state.transitionHasReversibleFlag(fromId, toId);
+
// This may fail if we're already on the transition, but that's okay!
selectDrawable(transitionIndex);
@@ -212,10 +208,14 @@ public class AnimatedStateListDrawable extends StateListDrawable {
final Drawable d = getCurrent();
if (d instanceof AnimationDrawable) {
final boolean reversed = state.isTransitionReversed(fromId, toId);
- transition = new AnimationDrawableTransition((AnimationDrawable) d, reversed);
+
+ transition = new AnimationDrawableTransition((AnimationDrawable) d,
+ reversed, hasReversibleFlag);
} else if (d instanceof AnimatedVectorDrawable) {
final boolean reversed = state.isTransitionReversed(fromId, toId);
- transition = new AnimatedVectorDrawableTransition((AnimatedVectorDrawable) d, reversed);
+
+ transition = new AnimatedVectorDrawableTransition((AnimatedVectorDrawable) d,
+ reversed, hasReversibleFlag);
} else if (d instanceof Animatable) {
transition = new AnimatableTransition((Animatable) d);
} else {
@@ -266,7 +266,12 @@ public class AnimatedStateListDrawable extends StateListDrawable {
private static class AnimationDrawableTransition extends Transition {
private final ObjectAnimator mAnim;
- public AnimationDrawableTransition(AnimationDrawable ad, boolean reversed) {
+ // Even AnimationDrawable is always reversible technically, but
+ // we should obey the XML's android:reversible flag.
+ private final boolean mHasReversibleFlag;
+
+ public AnimationDrawableTransition(AnimationDrawable ad,
+ boolean reversed, boolean hasReversibleFlag) {
final int frameCount = ad.getNumberOfFrames();
final int fromFrame = reversed ? frameCount - 1 : 0;
final int toFrame = reversed ? 0 : frameCount - 1;
@@ -275,13 +280,13 @@ public class AnimatedStateListDrawable extends StateListDrawable {
anim.setAutoCancel(true);
anim.setDuration(interp.getTotalDuration());
anim.setInterpolator(interp);
-
+ mHasReversibleFlag = hasReversibleFlag;
mAnim = anim;
}
@Override
public boolean canReverse() {
- return true;
+ return mHasReversibleFlag;
}
@Override
@@ -302,16 +307,28 @@ public class AnimatedStateListDrawable extends StateListDrawable {
private static class AnimatedVectorDrawableTransition extends Transition {
private final AnimatedVectorDrawable mAvd;
+
+ // mReversed is indicating the current transition's direction.
private final boolean mReversed;
- public AnimatedVectorDrawableTransition(AnimatedVectorDrawable avd, boolean reversed) {
+ // mHasReversibleFlag is indicating whether the whole transition has
+ // reversible flag set to true.
+ // If mHasReversibleFlag is false, then mReversed is always false.
+ private final boolean mHasReversibleFlag;
+
+ public AnimatedVectorDrawableTransition(AnimatedVectorDrawable avd,
+ boolean reversed, boolean hasReversibleFlag) {
mAvd = avd;
mReversed = reversed;
+ mHasReversibleFlag = hasReversibleFlag;
}
@Override
public boolean canReverse() {
- return mAvd.canReverse();
+ // When the transition's XML says it is not reversible, then we obey
+ // it, even if the AVD itself is reversible.
+ // This will help the single direction transition.
+ return mAvd.canReverse() && mHasReversibleFlag;
}
@Override
@@ -328,7 +345,8 @@ public class AnimatedStateListDrawable extends StateListDrawable {
if (canReverse()) {
mAvd.reverse();
} else {
- Log.w(LOGTAG, "Reverse() is called on a drawable can't reverse");
+ Log.w(LOGTAG, "Can't reverse, either the reversible is set to false,"
+ + " or the AnimatedVectorDrawable can't reverse");
}
}
@@ -359,24 +377,62 @@ public class AnimatedStateListDrawable extends StateListDrawable {
throws XmlPullParserException, IOException {
final TypedArray a = obtainAttributes(
r, theme, attrs, R.styleable.AnimatedStateListDrawable);
-
super.inflateWithAttributes(r, parser, a, R.styleable.AnimatedStateListDrawable_visible);
+ updateStateFromTypedArray(a);
+ a.recycle();
+
+ inflateChildElements(r, parser, attrs, theme);
- final StateListState stateListState = getStateListState();
- stateListState.setVariablePadding(a.getBoolean(
- R.styleable.AnimatedStateListDrawable_variablePadding, false));
- stateListState.setConstantSize(a.getBoolean(
- R.styleable.AnimatedStateListDrawable_constantSize, false));
- stateListState.setEnterFadeDuration(a.getInt(
- R.styleable.AnimatedStateListDrawable_enterFadeDuration, 0));
- stateListState.setExitFadeDuration(a.getInt(
- R.styleable.AnimatedStateListDrawable_exitFadeDuration, 0));
+ init();
+ }
+
+ @Override
+ public void applyTheme(@Nullable Theme theme) {
+ super.applyTheme(theme);
- setDither(a.getBoolean(R.styleable.AnimatedStateListDrawable_dither, true));
- setAutoMirrored(a.getBoolean(R.styleable.AnimatedStateListDrawable_autoMirrored, false));
+ final AnimatedStateListState state = mState;
+ if (state == null || state.mAnimThemeAttrs == null) {
+ return;
+ }
+ final TypedArray a = theme.resolveAttributes(
+ state.mAnimThemeAttrs, R.styleable.AnimatedRotateDrawable);
+ updateStateFromTypedArray(a);
a.recycle();
+ init();
+ }
+
+ private void updateStateFromTypedArray(TypedArray a) {
+ final AnimatedStateListState state = mState;
+
+ // Account for any configuration changes.
+ state.mChangingConfigurations |= a.getChangingConfigurations();
+
+ // Extract the theme attributes, if any.
+ state.mAnimThemeAttrs = a.extractThemeAttrs();
+
+ state.setVariablePadding(a.getBoolean(
+ R.styleable.AnimatedStateListDrawable_variablePadding, state.mVariablePadding));
+ state.setConstantSize(a.getBoolean(
+ R.styleable.AnimatedStateListDrawable_constantSize, state.mConstantSize));
+ state.setEnterFadeDuration(a.getInt(
+ R.styleable.AnimatedStateListDrawable_enterFadeDuration, state.mEnterFadeDuration));
+ state.setExitFadeDuration(a.getInt(
+ R.styleable.AnimatedStateListDrawable_exitFadeDuration, state.mExitFadeDuration));
+
+ setDither(a.getBoolean(
+ R.styleable.AnimatedStateListDrawable_dither, state.mDither));
+ setAutoMirrored(a.getBoolean(
+ R.styleable.AnimatedStateListDrawable_autoMirrored, state.mAutoMirrored));
+ }
+
+ private void init() {
+ onStateChange(getState());
+ }
+
+ private void inflateChildElements(Resources r, XmlPullParser parser, AttributeSet attrs,
+ Theme theme) throws XmlPullParserException, IOException {
int type;
final int innerDepth = parser.getDepth() + 1;
@@ -398,50 +454,36 @@ public class AnimatedStateListDrawable extends StateListDrawable {
parseTransition(r, parser, attrs, theme);
}
}
-
- onStateChange(getState());
}
private int parseTransition(@NonNull Resources r, @NonNull XmlPullParser parser,
@NonNull AttributeSet attrs, @Nullable Theme theme)
throws XmlPullParserException, IOException {
- int drawableRes = 0;
- int fromId = 0;
- int toId = 0;
- boolean reversible = false;
-
- final int numAttrs = attrs.getAttributeCount();
- for (int i = 0; i < numAttrs; i++) {
- final int stateResId = attrs.getAttributeNameResource(i);
- switch (stateResId) {
- case 0:
- break;
- case R.attr.fromId:
- fromId = attrs.getAttributeResourceValue(i, 0);
- break;
- case R.attr.toId:
- toId = attrs.getAttributeResourceValue(i, 0);
- break;
- case R.attr.drawable:
- drawableRes = attrs.getAttributeResourceValue(i, 0);
- break;
- case R.attr.reversible:
- reversible = attrs.getAttributeBooleanValue(i, false);
- break;
- }
- }
+ // This allows state list drawable item elements to be themed at
+ // inflation time but does NOT make them work for Zygote preload.
+ final TypedArray a = obtainAttributes(r, theme, attrs,
+ R.styleable.AnimatedStateListDrawableTransition);
+ final int fromId = a.getResourceId(
+ R.styleable.AnimatedStateListDrawableTransition_fromId, 0);
+ final int toId = a.getResourceId(
+ R.styleable.AnimatedStateListDrawableTransition_toId, 0);
+ final boolean reversible = a.getBoolean(
+ R.styleable.AnimatedStateListDrawableTransition_reversible, false);
+ Drawable dr = a.getDrawable(
+ R.styleable.AnimatedStateListDrawableTransition_drawable);
+ a.recycle();
- final Drawable dr;
- if (drawableRes != 0) {
- dr = r.getDrawable(drawableRes, theme);
- } else {
+ // Loading child elements modifies the state of the AttributeSet's
+ // underlying parser, so it needs to happen after obtaining
+ // attributes and extracting states.
+ if (dr == null) {
int type;
while ((type = parser.next()) == XmlPullParser.TEXT) {
}
if (type != XmlPullParser.START_TAG) {
throw new XmlPullParserException(
parser.getPositionDescription()
- + ": <item> tag requires a 'drawable' attribute or "
+ + ": <transition> tag requires a 'drawable' attribute or "
+ "child tag defining a drawable");
}
dr = Drawable.createFromXmlInner(r, parser, attrs, theme);
@@ -453,34 +495,20 @@ public class AnimatedStateListDrawable extends StateListDrawable {
private int parseItem(@NonNull Resources r, @NonNull XmlPullParser parser,
@NonNull AttributeSet attrs, @Nullable Theme theme)
throws XmlPullParserException, IOException {
- int drawableRes = 0;
- int keyframeId = 0;
-
- int j = 0;
- final int numAttrs = attrs.getAttributeCount();
- int[] states = new int[numAttrs];
- for (int i = 0; i < numAttrs; i++) {
- final int stateResId = attrs.getAttributeNameResource(i);
- switch (stateResId) {
- case 0:
- break;
- case R.attr.id:
- keyframeId = attrs.getAttributeResourceValue(i, 0);
- break;
- case R.attr.drawable:
- drawableRes = attrs.getAttributeResourceValue(i, 0);
- break;
- default:
- final boolean hasState = attrs.getAttributeBooleanValue(i, false);
- states[j++] = hasState ? stateResId : -stateResId;
- }
- }
- states = StateSet.trimStateSet(states, j);
+ // This allows state list drawable item elements to be themed at
+ // inflation time but does NOT make them work for Zygote preload.
+ final TypedArray a = obtainAttributes(r, theme, attrs,
+ R.styleable.AnimatedStateListDrawableItem);
+ final int keyframeId = a.getResourceId(R.styleable.AnimatedStateListDrawableItem_id, 0);
+ Drawable dr = a.getDrawable(R.styleable.AnimatedStateListDrawableItem_drawable);
+ a.recycle();
- final Drawable dr;
- if (drawableRes != 0) {
- dr = r.getDrawable(drawableRes, theme);
- } else {
+ final int[] states = extractStateSet(attrs);
+
+ // Loading child elements modifies the state of the AttributeSet's
+ // underlying parser, so it needs to happen after obtaining
+ // attributes and extracting states.
+ if (dr == null) {
int type;
while ((type = parser.next()) == XmlPullParser.TEXT) {
}
@@ -499,42 +527,71 @@ public class AnimatedStateListDrawable extends StateListDrawable {
@Override
public Drawable mutate() {
if (!mMutated && super.mutate() == this) {
- final AnimatedStateListState newState = new AnimatedStateListState(mState, this, null);
- setConstantState(newState);
+ mState.mutate();
mMutated = true;
}
return this;
}
+ @Override
+ AnimatedStateListState cloneConstantState() {
+ return new AnimatedStateListState(mState, this, null);
+ }
+
+ /**
+ * @hide
+ */
+ public void clearMutated() {
+ super.clearMutated();
+ mMutated = false;
+ }
+
static class AnimatedStateListState extends StateListState {
- private static final int REVERSE_SHIFT = 32;
- private static final int REVERSE_MASK = 0x1;
+ // REVERSED_BIT is indicating the current transition's direction.
+ private static final long REVERSED_BIT = 0x100000000l;
+
+ // REVERSIBLE_FLAG_BIT is indicating whether the whole transition has
+ // reversible flag set to true.
+ private static final long REVERSIBLE_FLAG_BIT = 0x200000000l;
- final LongSparseLongArray mTransitions;
- final SparseIntArray mStateIds;
+ int[] mAnimThemeAttrs;
+
+ LongSparseLongArray mTransitions;
+ SparseIntArray mStateIds;
AnimatedStateListState(@Nullable AnimatedStateListState orig,
@NonNull AnimatedStateListDrawable owner, @Nullable Resources res) {
super(orig, owner, res);
if (orig != null) {
- mTransitions = orig.mTransitions.clone();
- mStateIds = orig.mStateIds.clone();
+ // Perform a shallow copy and rely on mutate() to deep-copy.
+ mAnimThemeAttrs = orig.mAnimThemeAttrs;
+ mTransitions = orig.mTransitions;
+ mStateIds = orig.mStateIds;
} else {
mTransitions = new LongSparseLongArray();
mStateIds = new SparseIntArray();
}
}
+ private void mutate() {
+ mTransitions = mTransitions.clone();
+ mStateIds = mStateIds.clone();
+ }
+
int addTransition(int fromId, int toId, @NonNull Drawable anim, boolean reversible) {
final int pos = super.addChild(anim);
final long keyFromTo = generateTransitionKey(fromId, toId);
- mTransitions.append(keyFromTo, pos);
+ long reversibleBit = 0;
+ if (reversible) {
+ reversibleBit = REVERSIBLE_FLAG_BIT;
+ }
+ mTransitions.append(keyFromTo, pos | reversibleBit);
if (reversible) {
final long keyToFrom = generateTransitionKey(toId, fromId);
- mTransitions.append(keyToFrom, pos | (1L << REVERSE_SHIFT));
+ mTransitions.append(keyToFrom, pos | REVERSED_BIT | reversibleBit);
}
return addChild(anim);
@@ -566,7 +623,17 @@ public class AnimatedStateListDrawable extends StateListDrawable {
boolean isTransitionReversed(int fromId, int toId) {
final long keyFromTo = generateTransitionKey(fromId, toId);
- return (mTransitions.get(keyFromTo, -1) >> REVERSE_SHIFT & REVERSE_MASK) == 1;
+ return (mTransitions.get(keyFromTo, -1) & REVERSED_BIT) != 0;
+ }
+
+ boolean transitionHasReversibleFlag(int fromId, int toId) {
+ final long keyFromTo = generateTransitionKey(fromId, toId);
+ return (mTransitions.get(keyFromTo, -1) & REVERSIBLE_FLAG_BIT) != 0;
+ }
+
+ @Override
+ public boolean canApplyTheme() {
+ return mAnimThemeAttrs != null || super.canApplyTheme();
}
@Override
@@ -584,15 +651,19 @@ public class AnimatedStateListDrawable extends StateListDrawable {
}
}
- void setConstantState(@NonNull AnimatedStateListState state) {
+ @Override
+ protected void setConstantState(@NonNull DrawableContainerState state) {
super.setConstantState(state);
- mState = state;
+ if (state instanceof AnimatedStateListState) {
+ mState = (AnimatedStateListState) state;
+ }
}
private AnimatedStateListDrawable(@Nullable AnimatedStateListState state, @Nullable Resources res) {
super(null);
+ // Every animated state list drawable has its own constant state.
final AnimatedStateListState newState = new AnimatedStateListState(state, this, res);
setConstantState(newState);
onStateChange(getState());
diff --git a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
index ad0b415..2a17a60 100644
--- a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
+++ b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
@@ -16,7 +16,6 @@ package android.graphics.drawable;
import android.animation.Animator;
import android.animation.AnimatorInflater;
-import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.content.res.ColorStateList;
import android.content.res.Resources;
@@ -137,26 +136,32 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable {
private boolean mMutated;
public AnimatedVectorDrawable() {
- mAnimatedVectorState = new AnimatedVectorDrawableState(null);
+ this(null, null);
}
- private AnimatedVectorDrawable(AnimatedVectorDrawableState state, Resources res,
- Theme theme) {
- mAnimatedVectorState = new AnimatedVectorDrawableState(state);
- if (theme != null && canApplyTheme()) {
- applyTheme(theme);
- }
+ private AnimatedVectorDrawable(AnimatedVectorDrawableState state, Resources res) {
+ mAnimatedVectorState = new AnimatedVectorDrawableState(state, mCallback, res);
}
@Override
public Drawable mutate() {
if (!mMutated && super.mutate() == this) {
- mAnimatedVectorState.mVectorDrawable.mutate();
+ mAnimatedVectorState = new AnimatedVectorDrawableState(
+ mAnimatedVectorState, mCallback, null);
mMutated = true;
}
return this;
}
+ /**
+ * @hide
+ */
+ public void clearMutated() {
+ super.clearMutated();
+ mAnimatedVectorState.mVectorDrawable.clearMutated();
+ mMutated = false;
+ }
+
@Override
public ConstantState getConstantState() {
mAnimatedVectorState.mChangingConfigurations = getChangingConfigurations();
@@ -281,7 +286,11 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable {
VectorDrawable vectorDrawable = (VectorDrawable) res.getDrawable(
drawableRes, theme).mutate();
vectorDrawable.setAllowCaching(false);
+ vectorDrawable.setCallback(mCallback);
pathErrorScale = vectorDrawable.getPixelSize();
+ if (mAnimatedVectorState.mVectorDrawable != null) {
+ mAnimatedVectorState.mVectorDrawable.setCallback(null);
+ }
mAnimatedVectorState.mVectorDrawable = vectorDrawable;
}
a.recycle();
@@ -308,9 +317,8 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable {
@Override
public boolean canApplyTheme() {
- return super.canApplyTheme() || mAnimatedVectorState != null
- && mAnimatedVectorState.mVectorDrawable != null
- && mAnimatedVectorState.mVectorDrawable.canApplyTheme();
+ return (mAnimatedVectorState != null && mAnimatedVectorState.canApplyTheme())
+ || super.canApplyTheme();
}
@Override
@@ -329,14 +337,22 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable {
ArrayList<Animator> mAnimators;
ArrayMap<Animator, String> mTargetNameMap;
- public AnimatedVectorDrawableState(AnimatedVectorDrawableState copy) {
+ public AnimatedVectorDrawableState(AnimatedVectorDrawableState copy,
+ Callback owner, Resources res) {
if (copy != null) {
mChangingConfigurations = copy.mChangingConfigurations;
if (copy.mVectorDrawable != null) {
- mVectorDrawable = (VectorDrawable) copy.mVectorDrawable.getConstantState().newDrawable();
- mVectorDrawable.mutate();
- mVectorDrawable.setAllowCaching(false);
+ final ConstantState cs = copy.mVectorDrawable.getConstantState();
+ if (res != null) {
+ mVectorDrawable = (VectorDrawable) cs.newDrawable(res);
+ } else {
+ mVectorDrawable = (VectorDrawable) cs.newDrawable();
+ }
+ mVectorDrawable = (VectorDrawable) mVectorDrawable.mutate();
+ mVectorDrawable.setCallback(owner);
+ mVectorDrawable.setLayoutDirection(copy.mVectorDrawable.getLayoutDirection());
mVectorDrawable.setBounds(copy.mVectorDrawable.getBounds());
+ mVectorDrawable.setAllowCaching(false);
}
if (copy.mAnimators != null) {
final int numAnimators = copy.mAnimators.size();
@@ -358,18 +374,19 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable {
}
@Override
- public Drawable newDrawable() {
- return new AnimatedVectorDrawable(this, null, null);
+ public boolean canApplyTheme() {
+ return (mVectorDrawable != null && mVectorDrawable.canApplyTheme())
+ || super.canApplyTheme();
}
@Override
- public Drawable newDrawable(Resources res) {
- return new AnimatedVectorDrawable(this, res, null);
+ public Drawable newDrawable() {
+ return new AnimatedVectorDrawable(this, null);
}
@Override
- public Drawable newDrawable(Resources res, Theme theme) {
- return new AnimatedVectorDrawable(this, res, theme);
+ public Drawable newDrawable(Resources res) {
+ return new AnimatedVectorDrawable(this, res);
}
@Override
@@ -419,13 +436,16 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable {
@Override
public void start() {
+ // If any one of the animator has not ended, do nothing.
+ if (isStarted()) {
+ return;
+ }
+ // Otherwise, kick off every animator.
final ArrayList<Animator> animators = mAnimatedVectorState.mAnimators;
final int size = animators.size();
for (int i = 0; i < size; i++) {
final Animator animator = animators.get(i);
- if (!animator.isStarted()) {
- animator.start();
- }
+ animator.start();
}
invalidateSelf();
}
@@ -443,19 +463,22 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable {
/**
* Reverses ongoing animations or starts pending animations in reverse.
* <p>
- * NOTE: Only works of all animations are ValueAnimators.
+ * NOTE: Only works if all animations support reverse. Otherwise, this will
+ * do nothing.
* @hide
*/
public void reverse() {
+ // Only reverse when all the animators can be reverse. Otherwise, partially
+ // reverse is confusing.
+ if (!canReverse()) {
+ Log.w(LOGTAG, "AnimatedVectorDrawable can't reverse()");
+ return;
+ }
final ArrayList<Animator> animators = mAnimatedVectorState.mAnimators;
final int size = animators.size();
for (int i = 0; i < size; i++) {
final Animator animator = animators.get(i);
- if (animator.canReverse()) {
- animator.reverse();
- } else {
- Log.w(LOGTAG, "AnimatedVectorDrawable can't reverse()");
- }
+ animator.reverse();
}
}
@@ -473,4 +496,21 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable {
}
return true;
}
+
+ private final Callback mCallback = new Callback() {
+ @Override
+ public void invalidateDrawable(Drawable who) {
+ invalidateSelf();
+ }
+
+ @Override
+ public void scheduleDrawable(Drawable who, Runnable what, long when) {
+ scheduleSelf(what, when);
+ }
+
+ @Override
+ public void unscheduleDrawable(Drawable who, Runnable what) {
+ unscheduleSelf(what);
+ }
+ };
}
diff --git a/graphics/java/android/graphics/drawable/AnimationDrawable.java b/graphics/java/android/graphics/drawable/AnimationDrawable.java
index 9a9fd82..74ff1b0 100644
--- a/graphics/java/android/graphics/drawable/AnimationDrawable.java
+++ b/graphics/java/android/graphics/drawable/AnimationDrawable.java
@@ -16,11 +16,14 @@
package android.graphics.drawable;
+import com.android.internal.R;
+
import java.io.IOException;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
+import android.annotation.NonNull;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.Resources.Theme;
@@ -79,7 +82,7 @@ import android.util.AttributeSet;
* @attr ref android.R.styleable#AnimationDrawableItem_drawable
*/
public class AnimationDrawable extends DrawableContainer implements Runnable, Animatable {
- private final AnimationState mAnimationState;
+ private AnimationState mAnimationState;
/** The current frame, may be -1 when not animating. */
private int mCurFrame = -1;
@@ -271,27 +274,24 @@ public class AnimationDrawable extends DrawableContainer implements Runnable, An
@Override
public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
throws XmlPullParserException, IOException {
+ final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.AnimationDrawable);
+ super.inflateWithAttributes(r, parser, a, R.styleable.AnimationDrawable_visible);
+ updateStateFromTypedArray(a);
+ a.recycle();
- TypedArray a = obtainAttributes(r, theme, attrs,
- com.android.internal.R.styleable.AnimationDrawable);
-
- super.inflateWithAttributes(r, parser, a,
- com.android.internal.R.styleable.AnimationDrawable_visible);
-
- mAnimationState.setVariablePadding(a.getBoolean(
- com.android.internal.R.styleable.AnimationDrawable_variablePadding, false));
-
- mAnimationState.mOneShot = a.getBoolean(
- com.android.internal.R.styleable.AnimationDrawable_oneshot, false);
+ inflateChildElements(r, parser, attrs, theme);
- a.recycle();
+ setFrame(0, true, false);
+ }
+ private void inflateChildElements(Resources r, XmlPullParser parser, AttributeSet attrs,
+ Theme theme) throws XmlPullParserException, IOException {
int type;
final int innerDepth = parser.getDepth()+1;
int depth;
- while ((type=parser.next()) != XmlPullParser.END_DOCUMENT &&
- ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) {
+ while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
+ && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) {
if (type != XmlPullParser.START_TAG) {
continue;
}
@@ -300,31 +300,27 @@ public class AnimationDrawable extends DrawableContainer implements Runnable, An
continue;
}
- a = obtainAttributes(
- r, theme, attrs, com.android.internal.R.styleable.AnimationDrawableItem);
- int duration = a.getInt(
- com.android.internal.R.styleable.AnimationDrawableItem_duration, -1);
+ final TypedArray a = obtainAttributes(r, theme, attrs,
+ R.styleable.AnimationDrawableItem);
+
+ final int duration = a.getInt(R.styleable.AnimationDrawableItem_duration, -1);
if (duration < 0) {
- throw new XmlPullParserException(
- parser.getPositionDescription()
+ throw new XmlPullParserException(parser.getPositionDescription()
+ ": <item> tag requires a 'duration' attribute");
}
- int drawableRes = a.getResourceId(
- com.android.internal.R.styleable.AnimationDrawableItem_drawable, 0);
+
+ Drawable dr = a.getDrawable(R.styleable.AnimationDrawableItem_drawable);
a.recycle();
- Drawable dr;
- if (drawableRes != 0) {
- dr = r.getDrawable(drawableRes, theme);
- } else {
+ if (dr == null) {
while ((type=parser.next()) == XmlPullParser.TEXT) {
// Empty
}
if (type != XmlPullParser.START_TAG) {
- throw new XmlPullParserException(parser.getPositionDescription() +
- ": <item> tag requires a 'drawable' attribute or child tag" +
- " defining a drawable");
+ throw new XmlPullParserException(parser.getPositionDescription()
+ + ": <item> tag requires a 'drawable' attribute or child tag"
+ + " defining a drawable");
}
dr = Drawable.createFromXmlInner(r, parser, attrs, theme);
}
@@ -334,22 +330,41 @@ public class AnimationDrawable extends DrawableContainer implements Runnable, An
dr.setCallback(this);
}
}
+ }
- setFrame(0, true, false);
+ private void updateStateFromTypedArray(TypedArray a) {
+ mAnimationState.mVariablePadding = a.getBoolean(
+ R.styleable.AnimationDrawable_variablePadding, mAnimationState.mVariablePadding);
+
+ mAnimationState.mOneShot = a.getBoolean(
+ R.styleable.AnimationDrawable_oneshot, mAnimationState.mOneShot);
}
@Override
public Drawable mutate() {
if (!mMutated && super.mutate() == this) {
- mAnimationState.mDurations = mAnimationState.mDurations.clone();
+ mAnimationState.mutate();
mMutated = true;
}
return this;
}
+ @Override
+ AnimationState cloneConstantState() {
+ return new AnimationState(mAnimationState, this, null);
+ }
+
+ /**
+ * @hide
+ */
+ public void clearMutated() {
+ super.clearMutated();
+ mMutated = false;
+ }
+
private final static class AnimationState extends DrawableContainerState {
private int[] mDurations;
- private boolean mOneShot;
+ private boolean mOneShot = false;
AnimationState(AnimationState orig, AnimationDrawable owner,
Resources res) {
@@ -360,10 +375,14 @@ public class AnimationDrawable extends DrawableContainer implements Runnable, An
mOneShot = orig.mOneShot;
} else {
mDurations = new int[getCapacity()];
- mOneShot = true;
+ mOneShot = false;
}
}
+ private void mutate() {
+ mDurations = mDurations.clone();
+ }
+
@Override
public Drawable newDrawable() {
return new AnimationDrawable(this, null);
@@ -390,9 +409,17 @@ public class AnimationDrawable extends DrawableContainer implements Runnable, An
}
}
+ @Override
+ protected void setConstantState(@NonNull DrawableContainerState state) {
+ super.setConstantState(state);
+
+ if (state instanceof AnimationState) {
+ mAnimationState = (AnimationState) state;
+ }
+ }
+
private AnimationDrawable(AnimationState state, Resources res) {
- AnimationState as = new AnimationState(state, this, res);
- mAnimationState = as;
+ final AnimationState as = new AnimationState(state, this, res);
setConstantState(as);
if (state != null) {
setFrame(0, true, false);
diff --git a/graphics/java/android/graphics/drawable/BitmapDrawable.java b/graphics/java/android/graphics/drawable/BitmapDrawable.java
index cf6be48..9be296a 100644
--- a/graphics/java/android/graphics/drawable/BitmapDrawable.java
+++ b/graphics/java/android/graphics/drawable/BitmapDrawable.java
@@ -48,6 +48,7 @@ import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
+import java.util.Collection;
/**
* A Drawable that wraps a bitmap and can be tiled, stretched, or aligned. You can create a
@@ -132,7 +133,7 @@ public class BitmapDrawable extends Drawable {
*/
@Deprecated
public BitmapDrawable(Bitmap bitmap) {
- this(new BitmapState(bitmap), null, null);
+ this(new BitmapState(bitmap), null);
}
/**
@@ -140,7 +141,7 @@ public class BitmapDrawable extends Drawable {
* the display metrics of the resources.
*/
public BitmapDrawable(Resources res, Bitmap bitmap) {
- this(new BitmapState(bitmap), res, null);
+ this(new BitmapState(bitmap), res);
mBitmapState.mTargetDensity = mTargetDensity;
}
@@ -151,7 +152,7 @@ public class BitmapDrawable extends Drawable {
*/
@Deprecated
public BitmapDrawable(String filepath) {
- this(new BitmapState(BitmapFactory.decodeFile(filepath)), null, null);
+ this(new BitmapState(BitmapFactory.decodeFile(filepath)), null);
if (mBitmapState.mBitmap == null) {
android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + filepath);
}
@@ -162,7 +163,7 @@ public class BitmapDrawable extends Drawable {
*/
@SuppressWarnings("unused")
public BitmapDrawable(Resources res, String filepath) {
- this(new BitmapState(BitmapFactory.decodeFile(filepath)), null, null);
+ this(new BitmapState(BitmapFactory.decodeFile(filepath)), null);
mBitmapState.mTargetDensity = mTargetDensity;
if (mBitmapState.mBitmap == null) {
android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + filepath);
@@ -176,7 +177,7 @@ public class BitmapDrawable extends Drawable {
*/
@Deprecated
public BitmapDrawable(java.io.InputStream is) {
- this(new BitmapState(BitmapFactory.decodeStream(is)), null, null);
+ this(new BitmapState(BitmapFactory.decodeStream(is)), null);
if (mBitmapState.mBitmap == null) {
android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + is);
}
@@ -187,7 +188,7 @@ public class BitmapDrawable extends Drawable {
*/
@SuppressWarnings("unused")
public BitmapDrawable(Resources res, java.io.InputStream is) {
- this(new BitmapState(BitmapFactory.decodeStream(is)), null, null);
+ this(new BitmapState(BitmapFactory.decodeStream(is)), null);
mBitmapState.mTargetDensity = mTargetDensity;
if (mBitmapState.mBitmap == null) {
android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + is);
@@ -684,6 +685,14 @@ public class BitmapDrawable extends Drawable {
return this;
}
+ /**
+ * @hide
+ */
+ public void clearMutated() {
+ super.clearMutated();
+ mMutated = false;
+ }
+
@Override
protected boolean onStateChange(int[] stateSet) {
final BitmapState state = mBitmapState;
@@ -905,23 +914,21 @@ public class BitmapDrawable extends Drawable {
}
@Override
- public Bitmap getBitmap() {
- return mBitmap;
+ public int addAtlasableBitmaps(Collection<Bitmap> atlasList) {
+ if (isAtlasable(mBitmap) && atlasList.add(mBitmap)) {
+ return mBitmap.getWidth() * mBitmap.getHeight();
+ }
+ return 0;
}
@Override
public Drawable newDrawable() {
- return new BitmapDrawable(this, null, null);
+ return new BitmapDrawable(this, null);
}
@Override
public Drawable newDrawable(Resources res) {
- return new BitmapDrawable(this, res, null);
- }
-
- @Override
- public Drawable newDrawable(Resources res, Theme theme) {
- return new BitmapDrawable(this, res, theme);
+ return new BitmapDrawable(this, res);
}
@Override
@@ -934,16 +941,10 @@ public class BitmapDrawable extends Drawable {
* The one constructor to rule them all. This is called by all public
* constructors to set the state and initialize local properties.
*/
- private BitmapDrawable(BitmapState state, Resources res, Theme theme) {
- if (theme != null && state.canApplyTheme()) {
- // If we need to apply a theme, implicitly mutate.
- mBitmapState = new BitmapState(state);
- applyTheme(theme);
- } else {
- mBitmapState = state;
- }
+ private BitmapDrawable(BitmapState state, Resources res) {
+ mBitmapState = state;
- initializeWithState(state, res);
+ initializeWithState(mBitmapState, res);
}
/**
diff --git a/graphics/java/android/graphics/drawable/ClipDrawable.java b/graphics/java/android/graphics/drawable/ClipDrawable.java
index 40711cf..e5b2b76 100644
--- a/graphics/java/android/graphics/drawable/ClipDrawable.java
+++ b/graphics/java/android/graphics/drawable/ClipDrawable.java
@@ -16,6 +16,8 @@
package android.graphics.drawable;
+import com.android.internal.R;
+
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -49,12 +51,14 @@ import java.io.IOException;
* @attr ref android.R.styleable#ClipDrawable_drawable
*/
public class ClipDrawable extends Drawable implements Drawable.Callback {
- private ClipState mClipState;
+ private ClipState mState;
private final Rect mTmpRect = new Rect();
public static final int HORIZONTAL = 1;
public static final int VERTICAL = 2;
+ private boolean mMutated;
+
ClipDrawable() {
this(null, null);
}
@@ -65,9 +69,9 @@ public class ClipDrawable extends Drawable implements Drawable.Callback {
public ClipDrawable(Drawable drawable, int gravity, int orientation) {
this(null, null);
- mClipState.mDrawable = drawable;
- mClipState.mGravity = gravity;
- mClipState.mOrientation = orientation;
+ mState.mDrawable = drawable;
+ mState.mGravity = gravity;
+ mState.mOrientation = orientation;
if (drawable != null) {
drawable.setCallback(this);
@@ -79,19 +83,22 @@ public class ClipDrawable extends Drawable implements Drawable.Callback {
throws XmlPullParserException, IOException {
super.inflate(r, parser, attrs, theme);
- int type;
-
- TypedArray a = obtainAttributes(
- r, theme, attrs, com.android.internal.R.styleable.ClipDrawable);
+ final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.ClipDrawable);
- int orientation = a.getInt(
- com.android.internal.R.styleable.ClipDrawable_clipOrientation,
- HORIZONTAL);
- int g = a.getInt(com.android.internal.R.styleable.ClipDrawable_gravity, Gravity.LEFT);
- Drawable dr = a.getDrawable(com.android.internal.R.styleable.ClipDrawable_drawable);
+ // Reset mDrawable to preserve old multiple-inflate behavior. This is
+ // silly, but we have CTS tests that rely on it.
+ mState.mDrawable = null;
+ updateStateFromTypedArray(a);
+ inflateChildElements(r, parser, attrs, theme);
+ verifyRequiredAttributes(a);
a.recycle();
+ }
+ private void inflateChildElements(Resources r, XmlPullParser parser, AttributeSet attrs,
+ Theme theme) throws XmlPullParserException, IOException {
+ Drawable dr = null;
+ int type;
final int outerDepth = parser.getDepth();
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
@@ -101,15 +108,68 @@ public class ClipDrawable extends Drawable implements Drawable.Callback {
dr = Drawable.createFromXmlInner(r, parser, attrs, theme);
}
- if (dr == null) {
- throw new IllegalArgumentException("No drawable specified for <clip>");
+ if (dr != null) {
+ mState.mDrawable = dr;
+ dr.setCallback(this);
+ }
+ }
+
+ private void verifyRequiredAttributes(TypedArray a) throws XmlPullParserException {
+ // If we're not waiting on a theme, verify required attributes.
+ if (mState.mDrawable == null && (mState.mThemeAttrs == null
+ || mState.mThemeAttrs[R.styleable.ClipDrawable_drawable] == 0)) {
+ throw new XmlPullParserException(a.getPositionDescription()
+ + ": <clip> tag requires a 'drawable' attribute or "
+ + "child tag defining a drawable");
+ }
+ }
+
+ private void updateStateFromTypedArray(TypedArray a) {
+ final ClipState state = mState;
+
+ // 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);
+
+ final Drawable dr = a.getDrawable(R.styleable.ClipDrawable_drawable);
+ if (dr != null) {
+ state.mDrawable = dr;
+ dr.setCallback(this);
+ }
+ }
+
+ @Override
+ public void applyTheme(Theme t) {
+ super.applyTheme(t);
+
+ final ClipState state = mState;
+ if (state == null || state.mThemeAttrs == null) {
+ return;
+ }
+
+ final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.ClipDrawable);
+ try {
+ updateStateFromTypedArray(a);
+ verifyRequiredAttributes(a);
+ } catch (XmlPullParserException e) {
+ throw new RuntimeException(e);
+ } finally {
+ a.recycle();
}
- mClipState.mDrawable = dr;
- mClipState.mOrientation = orientation;
- mClipState.mGravity = g;
+ if (state.mDrawable != null && state.mDrawable.canApplyTheme()) {
+ state.mDrawable.applyTheme(t);
+ }
+ }
- dr.setCallback(this);
+ @Override
+ public boolean canApplyTheme() {
+ return (mState != null && mState.canApplyTheme()) || super.canApplyTheme();
}
// overrides from Drawable.Callback
@@ -143,78 +203,78 @@ public class ClipDrawable extends Drawable implements Drawable.Callback {
@Override
public int getChangingConfigurations() {
return super.getChangingConfigurations()
- | mClipState.mChangingConfigurations
- | mClipState.mDrawable.getChangingConfigurations();
+ | mState.mChangingConfigurations
+ | mState.mDrawable.getChangingConfigurations();
}
@Override
public boolean getPadding(Rect padding) {
// XXX need to adjust padding!
- return mClipState.mDrawable.getPadding(padding);
+ return mState.mDrawable.getPadding(padding);
}
@Override
public boolean setVisible(boolean visible, boolean restart) {
- mClipState.mDrawable.setVisible(visible, restart);
+ mState.mDrawable.setVisible(visible, restart);
return super.setVisible(visible, restart);
}
@Override
public void setAlpha(int alpha) {
- mClipState.mDrawable.setAlpha(alpha);
+ mState.mDrawable.setAlpha(alpha);
}
@Override
public int getAlpha() {
- return mClipState.mDrawable.getAlpha();
+ return mState.mDrawable.getAlpha();
}
@Override
public void setColorFilter(ColorFilter cf) {
- mClipState.mDrawable.setColorFilter(cf);
+ mState.mDrawable.setColorFilter(cf);
}
@Override
public void setTintList(ColorStateList tint) {
- mClipState.mDrawable.setTintList(tint);
+ mState.mDrawable.setTintList(tint);
}
@Override
public void setTintMode(Mode tintMode) {
- mClipState.mDrawable.setTintMode(tintMode);
+ mState.mDrawable.setTintMode(tintMode);
}
@Override
public int getOpacity() {
- return mClipState.mDrawable.getOpacity();
+ return mState.mDrawable.getOpacity();
}
@Override
public boolean isStateful() {
- return mClipState.mDrawable.isStateful();
+ return mState.mDrawable.isStateful();
}
@Override
protected boolean onStateChange(int[] state) {
- return mClipState.mDrawable.setState(state);
+ return mState.mDrawable.setState(state);
}
@Override
protected boolean onLevelChange(int level) {
- mClipState.mDrawable.setLevel(level);
+ mState.mDrawable.setLevel(level);
invalidateSelf();
return true;
}
@Override
protected void onBoundsChange(Rect bounds) {
- mClipState.mDrawable.setBounds(bounds);
+ mState.mDrawable.setBounds(bounds);
}
@Override
public void draw(Canvas canvas) {
- if (mClipState.mDrawable.getLevel() == 0) {
+ if (mState.mDrawable.getLevel() == 0) {
return;
}
@@ -222,41 +282,41 @@ public class ClipDrawable extends Drawable implements Drawable.Callback {
final Rect bounds = getBounds();
int level = getLevel();
int w = bounds.width();
- final int iw = 0; //mClipState.mDrawable.getIntrinsicWidth();
- if ((mClipState.mOrientation & HORIZONTAL) != 0) {
+ final int iw = 0; //mState.mDrawable.getIntrinsicWidth();
+ if ((mState.mOrientation & HORIZONTAL) != 0) {
w -= (w - iw) * (10000 - level) / 10000;
}
int h = bounds.height();
- final int ih = 0; //mClipState.mDrawable.getIntrinsicHeight();
- if ((mClipState.mOrientation & VERTICAL) != 0) {
+ final int ih = 0; //mState.mDrawable.getIntrinsicHeight();
+ if ((mState.mOrientation & VERTICAL) != 0) {
h -= (h - ih) * (10000 - level) / 10000;
}
final int layoutDirection = getLayoutDirection();
- Gravity.apply(mClipState.mGravity, w, h, bounds, r, layoutDirection);
+ Gravity.apply(mState.mGravity, w, h, bounds, r, layoutDirection);
if (w > 0 && h > 0) {
canvas.save();
canvas.clipRect(r);
- mClipState.mDrawable.draw(canvas);
+ mState.mDrawable.draw(canvas);
canvas.restore();
}
}
@Override
public int getIntrinsicWidth() {
- return mClipState.mDrawable.getIntrinsicWidth();
+ return mState.mDrawable.getIntrinsicWidth();
}
@Override
public int getIntrinsicHeight() {
- return mClipState.mDrawable.getIntrinsicHeight();
+ return mState.mDrawable.getIntrinsicHeight();
}
@Override
public ConstantState getConstantState() {
- if (mClipState.canConstantState()) {
- mClipState.mChangingConfigurations = getChangingConfigurations();
- return mClipState;
+ if (mState.canConstantState()) {
+ mState.mChangingConfigurations = getChangingConfigurations();
+ return mState;
}
return null;
}
@@ -264,21 +324,44 @@ public class ClipDrawable extends Drawable implements Drawable.Callback {
/** @hide */
@Override
public void setLayoutDirection(int layoutDirection) {
- mClipState.mDrawable.setLayoutDirection(layoutDirection);
+ mState.mDrawable.setLayoutDirection(layoutDirection);
super.setLayoutDirection(layoutDirection);
}
+ @Override
+ public Drawable mutate() {
+ if (!mMutated && super.mutate() == this) {
+ mState.mDrawable.mutate();
+ mMutated = true;
+ }
+ return this;
+ }
+
+ /**
+ * @hide
+ */
+ public void clearMutated() {
+ super.clearMutated();
+ mState.mDrawable.clearMutated();
+ mMutated = false;
+ }
+
final static class ClipState extends ConstantState {
- Drawable mDrawable;
+ int[] mThemeAttrs;
int mChangingConfigurations;
- int mOrientation;
- int mGravity;
+
+ Drawable mDrawable;
+
+ int mOrientation = HORIZONTAL;
+ int mGravity = Gravity.LEFT;
private boolean mCheckedConstantState;
private boolean mCanConstantState;
ClipState(ClipState orig, ClipDrawable owner, Resources res) {
if (orig != null) {
+ mThemeAttrs = orig.mThemeAttrs;
+ mChangingConfigurations = orig.mChangingConfigurations;
if (res != null) {
mDrawable = orig.mDrawable.getConstantState().newDrawable(res);
} else {
@@ -295,6 +378,12 @@ public class ClipDrawable extends Drawable implements Drawable.Callback {
}
@Override
+ public boolean canApplyTheme() {
+ return mThemeAttrs != null || (mDrawable != null && mDrawable.canApplyTheme())
+ || super.canApplyTheme();
+ }
+
+ @Override
public Drawable newDrawable() {
return new ClipDrawable(this, null);
}
@@ -320,7 +409,7 @@ public class ClipDrawable extends Drawable implements Drawable.Callback {
}
private ClipDrawable(ClipState state, Resources res) {
- mClipState = new ClipState(state, this, res);
+ mState = new ClipState(state, this, res);
}
}
diff --git a/graphics/java/android/graphics/drawable/ColorDrawable.java b/graphics/java/android/graphics/drawable/ColorDrawable.java
index 1253c46..e3b50ea 100644
--- a/graphics/java/android/graphics/drawable/ColorDrawable.java
+++ b/graphics/java/android/graphics/drawable/ColorDrawable.java
@@ -88,6 +88,14 @@ public class ColorDrawable extends Drawable {
return this;
}
+ /**
+ * @hide
+ */
+ public void clearMutated() {
+ super.clearMutated();
+ mMutated = false;
+ }
+
@Override
public void draw(Canvas canvas) {
final ColorFilter colorFilter = mPaint.getColorFilter();
@@ -244,6 +252,11 @@ public class ColorDrawable extends Drawable {
}
@Override
+ public boolean canApplyTheme() {
+ return mColorState.canApplyTheme() || super.canApplyTheme();
+ }
+
+ @Override
public void applyTheme(Theme t) {
super.applyTheme(t);
@@ -291,17 +304,12 @@ public class ColorDrawable extends Drawable {
@Override
public Drawable newDrawable() {
- return new ColorDrawable(this, null, null);
+ return new ColorDrawable(this);
}
@Override
public Drawable newDrawable(Resources res) {
- return new ColorDrawable(this, res, null);
- }
-
- @Override
- public Drawable newDrawable(Resources res, Theme theme) {
- return new ColorDrawable(this, res, theme);
+ return new ColorDrawable(this);
}
@Override
@@ -310,14 +318,8 @@ public class ColorDrawable extends Drawable {
}
}
- private ColorDrawable(ColorState state, Resources res, Theme theme) {
- if (theme != null && state.canApplyTheme()) {
- mColorState = new ColorState(state);
- applyTheme(theme);
- } else {
- mColorState = state;
- }
-
+ private ColorDrawable(ColorState state) {
+ mColorState = state;
mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
}
}
diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java
index 9ae788c..0e38cc0 100644
--- a/graphics/java/android/graphics/drawable/Drawable.java
+++ b/graphics/java/android/graphics/drawable/Drawable.java
@@ -51,6 +51,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.WeakReference;
import java.util.Arrays;
+import java.util.Collection;
/**
* A Drawable is a general abstraction for "something that can be drawn." Most
@@ -917,6 +918,20 @@ public abstract class Drawable {
}
/**
+ * Clears the mutated state, allowing this drawable to be cached and
+ * mutated again.
+ * <p>
+ * This is hidden because only framework drawables can be cached, so
+ * custom drawables don't need to support constant state, mutate(), or
+ * clearMutated().
+ *
+ * @hide
+ */
+ public void clearMutated() {
+ // Default implementation is no-op.
+ }
+
+ /**
* Create a drawable from an inputstream
*/
public static Drawable createFromStream(InputStream is, String srcName) {
@@ -1044,54 +1059,72 @@ public abstract class Drawable {
final Drawable drawable;
final String name = parser.getName();
- if (name.equals("selector")) {
- drawable = new StateListDrawable();
- } else if (name.equals("animated-selector")) {
- drawable = new AnimatedStateListDrawable();
- } else if (name.equals("level-list")) {
- drawable = new LevelListDrawable();
- } else if (name.equals("layer-list")) {
- drawable = new LayerDrawable();
- } else if (name.equals("transition")) {
- drawable = new TransitionDrawable();
- } else if (name.equals("ripple")) {
- drawable = new RippleDrawable();
- } else if (name.equals("color")) {
- drawable = new ColorDrawable();
- } else if (name.equals("shape")) {
- drawable = new GradientDrawable();
- } else if (name.equals("vector")) {
- drawable = new VectorDrawable();
- } else if (name.equals("animated-vector")) {
- drawable = new AnimatedVectorDrawable();
- } else if (name.equals("scale")) {
- drawable = new ScaleDrawable();
- } else if (name.equals("clip")) {
- drawable = new ClipDrawable();
- } else if (name.equals("rotate")) {
- drawable = new RotateDrawable();
- } else if (name.equals("animated-rotate")) {
- drawable = new AnimatedRotateDrawable();
- } else if (name.equals("animation-list")) {
- drawable = new AnimationDrawable();
- } else if (name.equals("inset")) {
- drawable = new InsetDrawable();
- } else if (name.equals("bitmap")) {
- //noinspection deprecation
- drawable = new BitmapDrawable(r);
- if (r != null) {
- ((BitmapDrawable) drawable).setTargetDensity(r.getDisplayMetrics());
- }
- } else if (name.equals("nine-patch")) {
- drawable = new NinePatchDrawable();
- if (r != null) {
- ((NinePatchDrawable) drawable).setTargetDensity(r.getDisplayMetrics());
- }
- } else {
- throw new XmlPullParserException(parser.getPositionDescription() +
- ": invalid drawable tag " + name);
- }
+ switch (name) {
+ case "selector":
+ drawable = new StateListDrawable();
+ break;
+ case "animated-selector":
+ drawable = new AnimatedStateListDrawable();
+ break;
+ case "level-list":
+ drawable = new LevelListDrawable();
+ break;
+ case "layer-list":
+ drawable = new LayerDrawable();
+ break;
+ case "transition":
+ drawable = new TransitionDrawable();
+ break;
+ case "ripple":
+ drawable = new RippleDrawable();
+ break;
+ case "color":
+ drawable = new ColorDrawable();
+ break;
+ case "shape":
+ drawable = new GradientDrawable();
+ break;
+ case "vector":
+ drawable = new VectorDrawable();
+ break;
+ case "animated-vector":
+ drawable = new AnimatedVectorDrawable();
+ break;
+ case "scale":
+ drawable = new ScaleDrawable();
+ break;
+ case "clip":
+ drawable = new ClipDrawable();
+ break;
+ case "rotate":
+ drawable = new RotateDrawable();
+ break;
+ case "animated-rotate":
+ drawable = new AnimatedRotateDrawable();
+ break;
+ case "animation-list":
+ drawable = new AnimationDrawable();
+ break;
+ case "inset":
+ drawable = new InsetDrawable();
+ break;
+ case "bitmap":
+ drawable = new BitmapDrawable(r);
+ if (r != null) {
+ ((BitmapDrawable) drawable).setTargetDensity(r.getDisplayMetrics());
+ }
+ break;
+ case "nine-patch":
+ drawable = new NinePatchDrawable();
+ if (r != null) {
+ ((NinePatchDrawable) drawable).setTargetDensity(r.getDisplayMetrics());
+ }
+ break;
+ default:
+ throw new XmlPullParserException(parser.getPositionDescription() +
+ ": invalid drawable tag " + name);
+ }
drawable.inflate(r, parser, attrs, theme);
return drawable;
}
@@ -1202,7 +1235,7 @@ public abstract class Drawable {
* implemented for drawables that can have a theme applied.
*/
public Drawable newDrawable(Resources res, Theme theme) {
- return newDrawable();
+ return newDrawable(null);
}
/**
@@ -1212,10 +1245,16 @@ public abstract class Drawable {
public abstract int getChangingConfigurations();
/**
+ * @return Total pixel count
* @hide
*/
- public Bitmap getBitmap() {
- return null;
+ public int addAtlasableBitmaps(Collection<Bitmap> atlasList) {
+ return 0;
+ }
+
+ /** @hide */
+ protected final boolean isAtlasable(Bitmap bitmap) {
+ return bitmap != null && bitmap.getConfig() == Bitmap.Config.ARGB_8888;
}
/**
diff --git a/graphics/java/android/graphics/drawable/DrawableContainer.java b/graphics/java/android/graphics/drawable/DrawableContainer.java
index 4a719fe..39ef10c 100644
--- a/graphics/java/android/graphics/drawable/DrawableContainer.java
+++ b/graphics/java/android/graphics/drawable/DrawableContainer.java
@@ -20,6 +20,7 @@ import android.annotation.NonNull;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
+import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Insets;
@@ -31,6 +32,8 @@ import android.os.SystemClock;
import android.util.LayoutDirection;
import android.util.SparseArray;
+import java.util.Collection;
+
/**
* A helper class that contains several {@link Drawable}s and selects which one to use.
*
@@ -54,19 +57,20 @@ public class DrawableContainer extends Drawable implements Drawable.Callback {
private DrawableContainerState mDrawableContainerState;
private Rect mHotspotBounds;
private Drawable mCurrDrawable;
+ private Drawable mLastDrawable;
private int mAlpha = 0xFF;
/** Whether setAlpha() has been called at least once. */
private boolean mHasAlpha;
private int mCurIndex = -1;
+ private int mLastIndex = -1;
private boolean mMutated;
// Animations.
private Runnable mAnimationRunnable;
private long mEnterAnimationEnd;
private long mExitAnimationEnd;
- private Drawable mLastDrawable;
// overrides from Drawable
@@ -255,6 +259,7 @@ public class DrawableContainer extends Drawable implements Drawable.Callback {
if (mLastDrawable != null) {
mLastDrawable.jumpToCurrentState();
mLastDrawable = null;
+ mLastIndex = -1;
changed = true;
}
if (mCurrDrawable != null) {
@@ -426,9 +431,11 @@ public class DrawableContainer extends Drawable implements Drawable.Callback {
}
if (mCurrDrawable != null) {
mLastDrawable = mCurrDrawable;
+ mLastIndex = mCurIndex;
mExitAnimationEnd = now + mDrawableContainerState.mExitFadeDuration;
} else {
mLastDrawable = null;
+ mLastIndex = -1;
mExitAnimationEnd = 0;
}
} else if (mCurrDrawable != null) {
@@ -440,36 +447,10 @@ public class DrawableContainer extends Drawable implements Drawable.Callback {
mCurrDrawable = d;
mCurIndex = idx;
if (d != null) {
- d.mutate();
if (mDrawableContainerState.mEnterFadeDuration > 0) {
mEnterAnimationEnd = now + mDrawableContainerState.mEnterFadeDuration;
- } else if (mHasAlpha) {
- d.setAlpha(mAlpha);
- }
- if (mDrawableContainerState.mHasColorFilter) {
- // Color filter always overrides tint.
- d.setColorFilter(mDrawableContainerState.mColorFilter);
- } else {
- if (mDrawableContainerState.mHasTintList) {
- d.setTintList(mDrawableContainerState.mTintList);
- }
- if (mDrawableContainerState.mHasTintMode) {
- d.setTintMode(mDrawableContainerState.mTintMode);
- }
- }
- d.setVisible(isVisible(), true);
- d.setDither(mDrawableContainerState.mDither);
- d.setState(getState());
- d.setLevel(getLevel());
- d.setBounds(getBounds());
- d.setLayoutDirection(getLayoutDirection());
- d.setAutoMirrored(mDrawableContainerState.mAutoMirrored);
-
- final Rect hotspotBounds = mHotspotBounds;
- if (hotspotBounds != null) {
- d.setHotspotBounds(hotspotBounds.left, hotspotBounds.top,
- hotspotBounds.right, hotspotBounds.bottom);
}
+ initializeDrawableForDisplay(d);
}
} else {
mCurrDrawable = null;
@@ -496,6 +477,45 @@ public class DrawableContainer extends Drawable implements Drawable.Callback {
return true;
}
+ /**
+ * Initializes a drawable for display in this container.
+ *
+ * @param d The drawable to initialize.
+ */
+ private void initializeDrawableForDisplay(Drawable d) {
+ d.mutate();
+
+ if (mDrawableContainerState.mEnterFadeDuration <= 0 && mHasAlpha) {
+ d.setAlpha(mAlpha);
+ }
+
+ if (mDrawableContainerState.mHasColorFilter) {
+ // Color filter always overrides tint.
+ d.setColorFilter(mDrawableContainerState.mColorFilter);
+ } else {
+ if (mDrawableContainerState.mHasTintList) {
+ d.setTintList(mDrawableContainerState.mTintList);
+ }
+ if (mDrawableContainerState.mHasTintMode) {
+ d.setTintMode(mDrawableContainerState.mTintMode);
+ }
+ }
+
+ d.setVisible(isVisible(), true);
+ d.setDither(mDrawableContainerState.mDither);
+ d.setState(getState());
+ d.setLevel(getLevel());
+ d.setBounds(getBounds());
+ d.setLayoutDirection(getLayoutDirection());
+ d.setAutoMirrored(mDrawableContainerState.mAutoMirrored);
+
+ final Rect hotspotBounds = mHotspotBounds;
+ if (hotspotBounds != null) {
+ d.setHotspotBounds(hotspotBounds.left, hotspotBounds.top,
+ hotspotBounds.right, hotspotBounds.bottom);
+ }
+ }
+
void animate(boolean schedule) {
mHasAlpha = true;
@@ -522,6 +542,7 @@ public class DrawableContainer extends Drawable implements Drawable.Callback {
if (mExitAnimationEnd <= now) {
mLastDrawable.setVisible(false, false);
mLastDrawable = null;
+ mLastIndex = -1;
mExitAnimationEnd = 0;
} else {
int animAlpha = (int)((mExitAnimationEnd-now)*255)
@@ -536,7 +557,7 @@ public class DrawableContainer extends Drawable implements Drawable.Callback {
}
if (schedule && animating) {
- scheduleSelf(mAnimationRunnable, now + 1000/60);
+ scheduleSelf(mAnimationRunnable, now + 1000 / 60);
}
}
@@ -567,13 +588,34 @@ public class DrawableContainer extends Drawable implements Drawable.Callback {
@Override
public Drawable mutate() {
if (!mMutated && super.mutate() == this) {
- mDrawableContainerState.mutate();
+ final DrawableContainerState clone = cloneConstantState();
+ clone.mutate();
+ setConstantState(clone);
mMutated = true;
}
return this;
}
/**
+ * Returns a shallow copy of the container's constant state to be used as
+ * the base state for {@link #mutate()}.
+ *
+ * @return a shallow copy of the constant state
+ */
+ DrawableContainerState cloneConstantState() {
+ return mDrawableContainerState;
+ }
+
+ /**
+ * @hide
+ */
+ public void clearMutated() {
+ super.clearMutated();
+ mDrawableContainerState.clearMutated();
+ mMutated = false;
+ }
+
+ /**
* A ConstantState that can contain several {@link Drawable}s.
*
* This class was made public to enable testing, and its visibility may change in a future
@@ -583,8 +625,6 @@ public class DrawableContainer extends Drawable implements Drawable.Callback {
final DrawableContainer mOwner;
final Resources mRes;
- Theme mTheme;
-
SparseArray<ConstantStateFuture> mDrawableFutures;
int mChangingConfigurations;
@@ -593,11 +633,11 @@ public class DrawableContainer extends Drawable implements Drawable.Callback {
Drawable[] mDrawables;
int mNumChildren;
- boolean mVariablePadding;
+ boolean mVariablePadding = false;
boolean mPaddingChecked;
Rect mConstantPadding;
- boolean mConstantSize;
+ boolean mConstantSize = false;
boolean mComputedConstantSize;
int mConstantWidth;
int mConstantHeight;
@@ -618,8 +658,8 @@ public class DrawableContainer extends Drawable implements Drawable.Callback {
boolean mMutated;
int mLayoutDirection;
- int mEnterFadeDuration;
- int mExitFadeDuration;
+ int mEnterFadeDuration = 0;
+ int mExitFadeDuration = 0;
boolean mAutoMirrored;
@@ -687,10 +727,17 @@ public class DrawableContainer extends Drawable implements Drawable.Callback {
mDrawableFutures = new SparseArray<ConstantStateFuture>(mNumChildren);
}
+ // Create futures for drawables with constant states. If a
+ // drawable doesn't have a constant state, then we can't clone
+ // it and we'll have to reference the original.
final int N = mNumChildren;
for (int i = 0; i < N; i++) {
if (origDr[i] != null) {
- mDrawableFutures.put(i, new ConstantStateFuture(origDr[i]));
+ if (origDr[i].getConstantState() != null) {
+ mDrawableFutures.put(i, new ConstantStateFuture(origDr[i]));
+ } else {
+ mDrawables[i] = origDr[i];
+ }
}
}
} else {
@@ -792,17 +839,17 @@ public class DrawableContainer extends Drawable implements Drawable.Callback {
}
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);
+ if (theme != null) {
+ createAllFutures();
+
+ final int N = mNumChildren;
+ final Drawable[] drawables = mDrawables;
+ for (int i = 0; i < N; i++) {
+ if (drawables[i] != null && drawables[i].canApplyTheme()) {
+ drawables[i].applyTheme(theme);
+ }
}
}
-
- mTheme = theme;
}
@Override
@@ -826,7 +873,7 @@ public class DrawableContainer extends Drawable implements Drawable.Callback {
return false;
}
- final void mutate() {
+ private void mutate() {
// No need to call createAllFutures, since future drawables will
// mutate when they are prepared.
final int N = mNumChildren;
@@ -840,6 +887,18 @@ public class DrawableContainer extends Drawable implements Drawable.Callback {
mMutated = true;
}
+ final void clearMutated() {
+ final int N = mNumChildren;
+ final Drawable[] drawables = mDrawables;
+ for (int i = 0; i < N; i++) {
+ if (drawables[i] != null) {
+ drawables[i].clearMutated();
+ }
+ }
+
+ mMutated = false;
+ }
+
/**
* A boolean value indicating whether to use the maximum padding value
* of all frames in the set (false), or to use the padding value of the
@@ -1026,6 +1085,20 @@ public class DrawableContainer extends Drawable implements Drawable.Callback {
return true;
}
+ /** @hide */
+ @Override
+ public int addAtlasableBitmaps(Collection<Bitmap> atlasList) {
+ final int N = mNumChildren;
+ int pixelCount = 0;
+ for (int i = 0; i < N; i++) {
+ final ConstantState state = getChild(i).getConstantState();
+ if (state != null) {
+ pixelCount += state.addAtlasableBitmaps(atlasList);
+ }
+ }
+ return pixelCount;
+ }
+
/**
* Class capable of cloning a Drawable from another Drawable's
* ConstantState.
@@ -1047,10 +1120,8 @@ public class DrawableContainer extends Drawable implements Drawable.Callback {
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 = mConstantState.newDrawable(state.mRes);
}
result.setLayoutDirection(state.mLayoutDirection);
result.setCallback(state.mOwner);
@@ -1074,5 +1145,18 @@ public class DrawableContainer extends Drawable implements Drawable.Callback {
protected void setConstantState(DrawableContainerState state) {
mDrawableContainerState = state;
+
+ // The locally cached drawables may have changed.
+ if (mCurIndex >= 0) {
+ mCurrDrawable = state.getChild(mCurIndex);
+ if (mCurrDrawable != null) {
+ initializeDrawableForDisplay(mCurrDrawable);
+ }
+ }
+
+ // Clear out the last drawable. We don't have enough information to
+ // propagate local state from the past.
+ mLastIndex = -1;
+ mLastDrawable = null;
}
}
diff --git a/graphics/java/android/graphics/drawable/GradientDrawable.java b/graphics/java/android/graphics/drawable/GradientDrawable.java
index cd6297b..cb42397 100644
--- a/graphics/java/android/graphics/drawable/GradientDrawable.java
+++ b/graphics/java/android/graphics/drawable/GradientDrawable.java
@@ -29,6 +29,8 @@ import android.graphics.Outline;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PixelFormat;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
import android.graphics.RadialGradient;
import android.graphics.Rect;
import android.graphics.RectF;
@@ -134,6 +136,7 @@ public class GradientDrawable extends Drawable {
private Rect mPadding;
private Paint mStrokePaint; // optional, set by the caller
private ColorFilter mColorFilter; // optional, set by the caller
+ private PorterDuffColorFilter mTintFilter;
private int mAlpha = 0xFF; // modified by the caller
private final Path mPath = new Path();
@@ -171,7 +174,7 @@ public class GradientDrawable extends Drawable {
}
public GradientDrawable() {
- this(new GradientState(Orientation.TOP_BOTTOM, null), null);
+ this(new GradientState(Orientation.TOP_BOTTOM, null));
}
/**
@@ -179,7 +182,7 @@ public class GradientDrawable extends Drawable {
* of colors for the gradient.
*/
public GradientDrawable(Orientation orientation, int[] colors) {
- this(new GradientState(orientation, colors), null);
+ this(new GradientState(orientation, colors));
}
@Override
@@ -523,13 +526,15 @@ public class GradientDrawable extends Drawable {
mStrokePaint.getStrokeWidth() > 0;
final boolean haveFill = currFillAlpha > 0;
final GradientState st = mGradientState;
+ final ColorFilter colorFilter = mColorFilter != null ? mColorFilter : mTintFilter;
+
/* we need a layer iff we're drawing both a fill and stroke, and the
stroke is non-opaque, and our shapetype actually supports
fill+stroke. Otherwise we can just draw the stroke (if any) on top
of the fill (if any) without worrying about blending artifacts.
*/
- final boolean useLayer = haveStroke && haveFill && st.mShape != LINE &&
- currStrokeAlpha < 255 && (mAlpha < 255 || mColorFilter != null);
+ final boolean useLayer = haveStroke && haveFill && st.mShape != LINE &&
+ currStrokeAlpha < 255 && (mAlpha < 255 || colorFilter != null);
/* Drawing with a layer is slower than direct drawing, but it
allows us to apply paint effects like alpha and colorfilter to
@@ -544,7 +549,7 @@ public class GradientDrawable extends Drawable {
}
mLayerPaint.setDither(st.mDither);
mLayerPaint.setAlpha(mAlpha);
- mLayerPaint.setColorFilter(mColorFilter);
+ mLayerPaint.setColorFilter(colorFilter);
float rad = mStrokePaint.getStrokeWidth();
canvas.saveLayer(mRect.left - rad, mRect.top - rad,
@@ -561,14 +566,14 @@ public class GradientDrawable extends Drawable {
*/
mFillPaint.setAlpha(currFillAlpha);
mFillPaint.setDither(st.mDither);
- mFillPaint.setColorFilter(mColorFilter);
- if (mColorFilter != null && st.mColorStateList == null) {
+ mFillPaint.setColorFilter(colorFilter);
+ if (colorFilter != null && st.mColorStateList == null) {
mFillPaint.setColor(mAlpha << 24);
}
if (haveStroke) {
mStrokePaint.setAlpha(currStrokeAlpha);
mStrokePaint.setDither(st.mDither);
- mStrokePaint.setColorFilter(mColorFilter);
+ mStrokePaint.setColorFilter(colorFilter);
}
}
@@ -593,7 +598,7 @@ public class GradientDrawable extends Drawable {
canvas.drawRoundRect(mRect, rad, rad, mStrokePaint);
}
} else {
- if (mFillPaint.getColor() != 0 || mColorFilter != null ||
+ if (mFillPaint.getColor() != 0 || colorFilter != null ||
mFillPaint.getShader() != null) {
canvas.drawRect(mRect, mFillPaint);
}
@@ -768,6 +773,11 @@ public class GradientDrawable extends Drawable {
}
}
+ if (s.mTint != null && s.mTintMode != null) {
+ mTintFilter = updateTintFilter(mTintFilter, s.mTint, s.mTintMode);
+ invalidateSelf = true;
+ }
+
if (invalidateSelf) {
invalidateSelf();
return true;
@@ -781,7 +791,8 @@ public class GradientDrawable extends Drawable {
final GradientState s = mGradientState;
return super.isStateful()
|| (s.mColorStateList != null && s.mColorStateList.isStateful())
- || (s.mStrokeColorStateList != null && s.mStrokeColorStateList.isStateful());
+ || (s.mStrokeColorStateList != null && s.mStrokeColorStateList.isStateful())
+ || (s.mTint != null && s.mTint.isStateful());
}
@Override
@@ -824,6 +835,20 @@ public class GradientDrawable extends Drawable {
}
@Override
+ public void setTintList(ColorStateList tint) {
+ mGradientState.mTint = tint;
+ mTintFilter = updateTintFilter(mTintFilter, tint, mGradientState.mTintMode);
+ invalidateSelf();
+ }
+
+ @Override
+ public void setTintMode(PorterDuff.Mode tintMode) {
+ mGradientState.mTintMode = tintMode;
+ mTintFilter = updateTintFilter(mTintFilter, mGradientState.mTint, tintMode);
+ invalidateSelf();
+ }
+
+ @Override
public int getOpacity() {
return (mAlpha == 255 && mGradientState.mOpaqueOverBounds && isOpaqueForState()) ?
PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT;
@@ -918,7 +943,11 @@ public class GradientDrawable extends Drawable {
float radius = st.mGradientRadius;
if (st.mGradientRadiusType == RADIUS_TYPE_FRACTION) {
- radius *= Math.min(st.mWidth, st.mHeight);
+ // Fall back to parent width or height if intrinsic
+ // size is not specified.
+ final float width = st.mWidth >= 0 ? st.mWidth : r.width();
+ final float height = st.mHeight >= 0 ? st.mHeight : r.height();
+ radius *= Math.min(width, height);
} else if (st.mGradientRadiusType == RADIUS_TYPE_FRACTION_PARENT) {
radius *= Math.min(r.width(), r.height());
}
@@ -929,9 +958,9 @@ public class GradientDrawable extends Drawable {
mGradientRadius = radius;
- if (radius == 0) {
- // We can't have a shader with zero radius, so let's
- // have a very, very small radius.
+ if (radius <= 0) {
+ // We can't have a shader with non-positive radius, so
+ // let's have a very, very small radius.
radius = 0.001f;
}
@@ -997,13 +1026,16 @@ public class GradientDrawable extends Drawable {
super.applyTheme(t);
final GradientState state = mGradientState;
- if (state == null || state.mThemeAttrs == null) {
+ if (state == null) {
return;
}
- final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.GradientDrawable);
- updateStateFromTypedArray(a);
- a.recycle();
+ if (state.mThemeAttrs != null) {
+ final TypedArray a = t.resolveAttributes(
+ state.mThemeAttrs, R.styleable.GradientDrawable);
+ updateStateFromTypedArray(a);
+ a.recycle();
+ }
applyThemeChildElements(t);
@@ -1045,15 +1077,23 @@ public class GradientDrawable extends Drawable {
state.mUseLevelForShape = a.getBoolean(
R.styleable.GradientDrawable_useLevel, state.mUseLevelForShape);
}
+
+ final int tintMode = a.getInt(R.styleable.GradientDrawable_tintMode, -1);
+ if (tintMode != -1) {
+ state.mTintMode = Drawable.parseTintMode(tintMode, PorterDuff.Mode.SRC_IN);
+ }
+
+ final ColorStateList tint = a.getColorStateList(R.styleable.GradientDrawable_tint);
+ if (tint != null) {
+ state.mTint = tint;
+ }
+
+ mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
}
@Override
public boolean canApplyTheme() {
- final GradientState st = mGradientState;
- return st != null && (st.mThemeAttrs != null || st.mAttrSize != null
- || st.mAttrGradient != null || st.mAttrSolid != null
- || st.mAttrStroke != null || st.mAttrCorners != null
- || st.mAttrPadding != null);
+ return (mGradientState != null && mGradientState.canApplyTheme()) || super.canApplyTheme();
}
private void applyThemeChildElements(Theme t) {
@@ -1359,9 +1399,12 @@ public class GradientDrawable extends Drawable {
} else {
radiusType = RADIUS_TYPE_FRACTION;
}
- } else {
+ } else if (tv.type == TypedValue.TYPE_DIMENSION) {
radius = tv.getDimension(r.getDisplayMetrics());
radiusType = RADIUS_TYPE_PIXELS;
+ } else {
+ radius = tv.getFloat();
+ radiusType = RADIUS_TYPE_PIXELS;
}
st.mGradientRadius = radius;
@@ -1479,6 +1522,14 @@ public class GradientDrawable extends Drawable {
return this;
}
+ /**
+ * @hide
+ */
+ public void clearMutated() {
+ super.clearMutated();
+ mMutated = false;
+ }
+
final static class GradientState extends ConstantState {
public int mChangingConfigurations;
public int mShape = RECTANGLE;
@@ -1505,14 +1556,18 @@ public class GradientDrawable extends Drawable {
public int mThickness = -1;
public boolean mDither = false;
- private float mCenterX = 0.5f;
- private float mCenterY = 0.5f;
- private float mGradientRadius = 0.5f;
- private int mGradientRadiusType = RADIUS_TYPE_PIXELS;
- private boolean mUseLevel;
- private boolean mUseLevelForShape;
- private boolean mOpaqueOverBounds;
- private boolean mOpaqueOverShape;
+ float mCenterX = 0.5f;
+ float mCenterY = 0.5f;
+ float mGradientRadius = 0.5f;
+ int mGradientRadiusType = RADIUS_TYPE_PIXELS;
+ boolean mUseLevel = false;
+ boolean mUseLevelForShape = true;
+
+ boolean mOpaqueOverBounds;
+ boolean mOpaqueOverShape;
+
+ ColorStateList mTint = null;
+ PorterDuff.Mode mTintMode = DEFAULT_TINT_MODE;
int[] mThemeAttrs;
int[] mAttrSize;
@@ -1566,6 +1621,8 @@ public class GradientDrawable extends Drawable {
mUseLevelForShape = state.mUseLevelForShape;
mOpaqueOverBounds = state.mOpaqueOverBounds;
mOpaqueOverShape = state.mOpaqueOverShape;
+ mTint = state.mTint;
+ mTintMode = state.mTintMode;
mThemeAttrs = state.mThemeAttrs;
mAttrSize = state.mAttrSize;
mAttrGradient = state.mAttrGradient;
@@ -1577,22 +1634,19 @@ public class GradientDrawable extends Drawable {
@Override
public boolean canApplyTheme() {
- return mThemeAttrs != null;
+ return mThemeAttrs != null || mAttrSize != null || mAttrGradient != null
+ || mAttrSolid != null || mAttrStroke != null || mAttrCorners != null
+ || mAttrPadding != null || super.canApplyTheme();
}
@Override
public Drawable newDrawable() {
- return new GradientDrawable(this, null);
+ return new GradientDrawable(this);
}
@Override
public Drawable newDrawable(Resources res) {
- return new GradientDrawable(this, null);
- }
-
- @Override
- public Drawable newDrawable(Resources res, Theme theme) {
- return new GradientDrawable(this, theme);
+ return new GradientDrawable(this);
}
@Override
@@ -1696,18 +1750,11 @@ public class GradientDrawable extends Drawable {
* 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) {
- if (theme != null && state.canApplyTheme()) {
- // If we need to apply a theme, implicitly mutate.
- mGradientState = new GradientState(state);
- applyTheme(theme);
- } else {
- mGradientState = state;
- }
+ private GradientDrawable(GradientState state) {
+ mGradientState = state;
- initializeWithState(state);
+ initializeWithState(mGradientState);
mGradientIsDirty = true;
mMutated = false;
@@ -1748,5 +1795,7 @@ public class GradientDrawable extends Drawable {
mStrokePaint.setPathEffect(e);
}
}
+
+ mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
}
}
diff --git a/graphics/java/android/graphics/drawable/InsetDrawable.java b/graphics/java/android/graphics/drawable/InsetDrawable.java
index 961d160..8b70a08 100644
--- a/graphics/java/android/graphics/drawable/InsetDrawable.java
+++ b/graphics/java/android/graphics/drawable/InsetDrawable.java
@@ -19,6 +19,7 @@ package android.graphics.drawable;
import com.android.internal.R;
import android.annotation.NonNull;
+
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -26,15 +27,19 @@ import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.Resources.Theme;
+import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Insets;
import android.graphics.Outline;
+import android.graphics.PixelFormat;
import android.graphics.PorterDuff.Mode;
+import android.graphics.drawable.Drawable.ConstantState;
import android.graphics.Rect;
import android.util.AttributeSet;
import java.io.IOException;
+import java.util.Collection;
/**
* A Drawable that insets another Drawable by a specified distance.
@@ -55,7 +60,8 @@ import java.io.IOException;
public class InsetDrawable extends Drawable implements Drawable.Callback {
private final Rect mTmpRect = new Rect();
- private InsetState mInsetState;
+ private final InsetState mState;
+
private boolean mMutated;
/*package*/ InsetDrawable() {
@@ -70,11 +76,11 @@ public class InsetDrawable extends Drawable implements Drawable.Callback {
int insetRight, int insetBottom) {
this(null, null);
- mInsetState.mDrawable = drawable;
- mInsetState.mInsetLeft = insetLeft;
- mInsetState.mInsetTop = insetTop;
- mInsetState.mInsetRight = insetRight;
- mInsetState.mInsetBottom = insetBottom;
+ mState.mDrawable = drawable;
+ mState.mInsetLeft = insetLeft;
+ mState.mInsetTop = insetTop;
+ mState.mInsetRight = insetRight;
+ mState.mInsetBottom = insetBottom;
if (drawable != null) {
drawable.setCallback(this);
@@ -87,11 +93,20 @@ public class InsetDrawable extends Drawable implements Drawable.Callback {
final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.InsetDrawable);
super.inflateWithAttributes(r, parser, a, R.styleable.InsetDrawable_visible);
- mInsetState.mDrawable = null;
+ // Reset mDrawable to preserve old multiple-inflate behavior. This is
+ // silly, but we have CTS tests that rely on it.
+ mState.mDrawable = null;
+
updateStateFromTypedArray(a);
+ inflateChildElements(r, parser, attrs, theme);
+ verifyRequiredAttributes(a);
+ a.recycle();
+ }
+ private void inflateChildElements(Resources r, XmlPullParser parser, AttributeSet attrs,
+ Theme theme) throws XmlPullParserException, IOException {
// Load inner XML elements.
- if (mInsetState.mDrawable == null) {
+ if (mState.mDrawable == null) {
int type;
while ((type=parser.next()) == XmlPullParser.TEXT) {
}
@@ -102,26 +117,23 @@ public class InsetDrawable extends Drawable implements Drawable.Callback {
+ "child tag defining a drawable");
}
final Drawable dr = Drawable.createFromXmlInner(r, parser, attrs, theme);
- mInsetState.mDrawable = dr;
+ mState.mDrawable = dr;
dr.setCallback(this);
}
-
- verifyRequiredAttributes(a);
- a.recycle();
}
private void verifyRequiredAttributes(TypedArray a) throws XmlPullParserException {
// If we're not waiting on a theme, verify required attributes.
- if (mInsetState.mDrawable == null && (mInsetState.mThemeAttrs == null
- || mInsetState.mThemeAttrs[R.styleable.InsetDrawable_drawable] == 0)) {
- throw new XmlPullParserException(a.getPositionDescription() +
- ": <inset> tag requires a 'drawable' attribute or "
+ if (mState.mDrawable == null && (mState.mThemeAttrs == null
+ || mState.mThemeAttrs[R.styleable.InsetDrawable_drawable] == 0)) {
+ throw new XmlPullParserException(a.getPositionDescription()
+ + ": <inset> tag requires a 'drawable' attribute or "
+ "child tag defining a drawable");
}
}
private void updateStateFromTypedArray(TypedArray a) throws XmlPullParserException {
- final InsetState state = mInsetState;
+ final InsetState state = mState;
// Account for any configuration changes.
state.mChangingConfigurations |= a.getChangingConfigurations();
@@ -169,25 +181,31 @@ public class InsetDrawable extends Drawable implements Drawable.Callback {
public void applyTheme(Theme t) {
super.applyTheme(t);
- final InsetState state = mInsetState;
- if (state == null || state.mThemeAttrs == null) {
+ final InsetState state = mState;
+ if (state == null) {
return;
}
- final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.InsetDrawable);
- try {
- updateStateFromTypedArray(a);
- verifyRequiredAttributes(a);
- } catch (XmlPullParserException e) {
- throw new RuntimeException(e);
- } finally {
- a.recycle();
+ if (state.mThemeAttrs != null) {
+ final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.InsetDrawable);
+ try {
+ updateStateFromTypedArray(a);
+ verifyRequiredAttributes(a);
+ } catch (XmlPullParserException e) {
+ throw new RuntimeException(e);
+ } finally {
+ a.recycle();
+ }
+ }
+
+ if (state.mDrawable != null && state.mDrawable.canApplyTheme()) {
+ state.mDrawable.applyTheme(t);
}
}
@Override
public boolean canApplyTheme() {
- return mInsetState != null && mInsetState.mThemeAttrs != null;
+ return (mState != null && mState.canApplyTheme()) || super.canApplyTheme();
}
@Override
@@ -216,112 +234,118 @@ public class InsetDrawable extends Drawable implements Drawable.Callback {
@Override
public void draw(Canvas canvas) {
- mInsetState.mDrawable.draw(canvas);
+ mState.mDrawable.draw(canvas);
}
@Override
public int getChangingConfigurations() {
return super.getChangingConfigurations()
- | mInsetState.mChangingConfigurations
- | mInsetState.mDrawable.getChangingConfigurations();
+ | mState.mChangingConfigurations
+ | mState.mDrawable.getChangingConfigurations();
}
@Override
public boolean getPadding(Rect padding) {
- boolean pad = mInsetState.mDrawable.getPadding(padding);
+ boolean pad = mState.mDrawable.getPadding(padding);
- padding.left += mInsetState.mInsetLeft;
- padding.right += mInsetState.mInsetRight;
- padding.top += mInsetState.mInsetTop;
- padding.bottom += mInsetState.mInsetBottom;
+ padding.left += mState.mInsetLeft;
+ padding.right += mState.mInsetRight;
+ padding.top += mState.mInsetTop;
+ padding.bottom += mState.mInsetBottom;
- return pad || (mInsetState.mInsetLeft | mInsetState.mInsetRight |
- mInsetState.mInsetTop | mInsetState.mInsetBottom) != 0;
+ return pad || (mState.mInsetLeft | mState.mInsetRight |
+ mState.mInsetTop | mState.mInsetBottom) != 0;
}
/** @hide */
@Override
public Insets getOpticalInsets() {
final Insets contentInsets = super.getOpticalInsets();
- return Insets.of(contentInsets.left + mInsetState.mInsetLeft,
- contentInsets.top + mInsetState.mInsetTop,
- contentInsets.right + mInsetState.mInsetRight,
- contentInsets.bottom + mInsetState.mInsetBottom);
+ return Insets.of(contentInsets.left + mState.mInsetLeft,
+ contentInsets.top + mState.mInsetTop,
+ contentInsets.right + mState.mInsetRight,
+ contentInsets.bottom + mState.mInsetBottom);
}
@Override
public void setHotspot(float x, float y) {
- mInsetState.mDrawable.setHotspot(x, y);
+ mState.mDrawable.setHotspot(x, y);
}
@Override
public void setHotspotBounds(int left, int top, int right, int bottom) {
- mInsetState.mDrawable.setHotspotBounds(left, top, right, bottom);
+ mState.mDrawable.setHotspotBounds(left, top, right, bottom);
}
/** @hide */
@Override
public void getHotspotBounds(Rect outRect) {
- mInsetState.mDrawable.getHotspotBounds(outRect);
+ mState.mDrawable.getHotspotBounds(outRect);
}
@Override
public boolean setVisible(boolean visible, boolean restart) {
- mInsetState.mDrawable.setVisible(visible, restart);
+ mState.mDrawable.setVisible(visible, restart);
return super.setVisible(visible, restart);
}
@Override
public void setAlpha(int alpha) {
- mInsetState.mDrawable.setAlpha(alpha);
+ mState.mDrawable.setAlpha(alpha);
}
@Override
public int getAlpha() {
- return mInsetState.mDrawable.getAlpha();
+ return mState.mDrawable.getAlpha();
}
@Override
public void setColorFilter(ColorFilter cf) {
- mInsetState.mDrawable.setColorFilter(cf);
+ mState.mDrawable.setColorFilter(cf);
}
@Override
public void setTintList(ColorStateList tint) {
- mInsetState.mDrawable.setTintList(tint);
+ mState.mDrawable.setTintList(tint);
}
@Override
public void setTintMode(Mode tintMode) {
- mInsetState.mDrawable.setTintMode(tintMode);
+ mState.mDrawable.setTintMode(tintMode);
}
/** {@hide} */
@Override
public void setLayoutDirection(int layoutDirection) {
- mInsetState.mDrawable.setLayoutDirection(layoutDirection);
+ mState.mDrawable.setLayoutDirection(layoutDirection);
}
@Override
public int getOpacity() {
- return mInsetState.mDrawable.getOpacity();
+ final InsetState state = mState;
+ final int opacity = state.mDrawable.getOpacity();
+ if (opacity == PixelFormat.OPAQUE && (state.mInsetLeft > 0 || state.mInsetTop > 0
+ || state.mInsetRight > 0 || state.mInsetBottom > 0)) {
+ return PixelFormat.TRANSLUCENT;
+ }
+ return opacity;
}
@Override
public boolean isStateful() {
- return mInsetState.mDrawable.isStateful();
+ return mState.mDrawable.isStateful();
}
@Override
protected boolean onStateChange(int[] state) {
- boolean changed = mInsetState.mDrawable.setState(state);
+ boolean changed = mState.mDrawable.setState(state);
onBoundsChange(getBounds());
return changed;
}
@Override
protected boolean onLevelChange(int level) {
- return mInsetState.mDrawable.setLevel(level);
+ return mState.mDrawable.setLevel(level);
}
@Override
@@ -329,34 +353,36 @@ public class InsetDrawable extends Drawable implements Drawable.Callback {
final Rect r = mTmpRect;
r.set(bounds);
- r.left += mInsetState.mInsetLeft;
- r.top += mInsetState.mInsetTop;
- r.right -= mInsetState.mInsetRight;
- r.bottom -= mInsetState.mInsetBottom;
+ r.left += mState.mInsetLeft;
+ r.top += mState.mInsetTop;
+ r.right -= mState.mInsetRight;
+ r.bottom -= mState.mInsetBottom;
- mInsetState.mDrawable.setBounds(r.left, r.top, r.right, r.bottom);
+ mState.mDrawable.setBounds(r.left, r.top, r.right, r.bottom);
}
@Override
public int getIntrinsicWidth() {
- return mInsetState.mDrawable.getIntrinsicWidth();
+ return mState.mDrawable.getIntrinsicWidth()
+ + mState.mInsetLeft + mState.mInsetRight;
}
@Override
public int getIntrinsicHeight() {
- return mInsetState.mDrawable.getIntrinsicHeight();
+ return mState.mDrawable.getIntrinsicHeight()
+ + mState.mInsetTop + mState.mInsetBottom;
}
@Override
public void getOutline(@NonNull Outline outline) {
- mInsetState.mDrawable.getOutline(outline);
+ mState.mDrawable.getOutline(outline);
}
@Override
public ConstantState getConstantState() {
- if (mInsetState.canConstantState()) {
- mInsetState.mChangingConfigurations = getChangingConfigurations();
- return mInsetState;
+ if (mState.canConstantState()) {
+ mState.mChangingConfigurations = getChangingConfigurations();
+ return mState;
}
return null;
}
@@ -364,17 +390,26 @@ public class InsetDrawable extends Drawable implements Drawable.Callback {
@Override
public Drawable mutate() {
if (!mMutated && super.mutate() == this) {
- mInsetState.mDrawable.mutate();
+ mState.mDrawable.mutate();
mMutated = true;
}
return this;
}
/**
+ * @hide
+ */
+ public void clearMutated() {
+ super.clearMutated();
+ mState.mDrawable.clearMutated();
+ mMutated = false;
+ }
+
+ /**
* Returns the drawable wrapped by this InsetDrawable. May be null.
*/
public Drawable getDrawable() {
- return mInsetState.mDrawable;
+ return mState.mDrawable;
}
final static class InsetState extends ConstantState {
@@ -383,13 +418,13 @@ public class InsetDrawable extends Drawable implements Drawable.Callback {
Drawable mDrawable;
- int mInsetLeft;
- int mInsetTop;
- int mInsetRight;
- int mInsetBottom;
+ int mInsetLeft = 0;
+ int mInsetTop = 0;
+ int mInsetRight = 0;
+ int mInsetBottom = 0;
- boolean mCheckedConstantState;
- boolean mCanConstantState;
+ private boolean mCheckedConstantState;
+ private boolean mCanConstantState;
InsetState(InsetState orig, InsetDrawable owner, Resources res) {
if (orig != null) {
@@ -413,6 +448,12 @@ public class InsetDrawable extends Drawable implements Drawable.Callback {
}
@Override
+ public boolean canApplyTheme() {
+ return mThemeAttrs != null || (mDrawable != null && mDrawable.canApplyTheme())
+ || super.canApplyTheme();
+ }
+
+ @Override
public Drawable newDrawable() {
return new InsetDrawable(this, null);
}
@@ -435,10 +476,19 @@ public class InsetDrawable extends Drawable implements Drawable.Callback {
return mCanConstantState;
}
+
+ @Override
+ public int addAtlasableBitmaps(Collection<Bitmap> atlasList) {
+ final ConstantState state = mDrawable.getConstantState();
+ if (state != null) {
+ return state.addAtlasableBitmaps(atlasList);
+ }
+ return 0;
+ }
}
private InsetDrawable(InsetState state, Resources res) {
- mInsetState = new InsetState(state, this, res);
+ mState = new InsetState(state, this, res);
}
}
diff --git a/graphics/java/android/graphics/drawable/LayerDrawable.java b/graphics/java/android/graphics/drawable/LayerDrawable.java
index 001ed88..4aa5f59 100644
--- a/graphics/java/android/graphics/drawable/LayerDrawable.java
+++ b/graphics/java/android/graphics/drawable/LayerDrawable.java
@@ -21,6 +21,7 @@ import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
import android.content.res.TypedArray;
+import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Outline;
@@ -36,6 +37,7 @@ import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
+import java.util.Collection;
/**
* A Drawable that manages an array of other Drawables. These are drawn in array
@@ -100,10 +102,10 @@ public class LayerDrawable extends Drawable implements Drawable.Callback {
* @param state The constant drawable state.
*/
LayerDrawable(Drawable[] layers, LayerState state) {
- this(state, null, null);
- int length = layers.length;
- ChildDrawable[] r = new ChildDrawable[length];
+ this(state, null);
+ final int length = layers.length;
+ final ChildDrawable[] r = new ChildDrawable[length];
for (int i = 0; i < length; i++) {
r[i] = new ChildDrawable();
r[i].mDrawable = layers[i];
@@ -117,18 +119,14 @@ public class LayerDrawable extends Drawable implements Drawable.Callback {
}
LayerDrawable() {
- this((LayerState) null, null, null);
+ this((LayerState) null, null);
}
- LayerDrawable(LayerState state, Resources res, Theme theme) {
- final LayerState as = createConstantState(state, res);
- mLayerState = as;
- if (as.mNum > 0) {
+ LayerDrawable(LayerState state, Resources res) {
+ mLayerState = createConstantState(state, res);
+ if (mLayerState.mNum > 0) {
ensurePadding();
}
- if (theme != null && canApplyTheme()) {
- applyTheme(theme);
- }
}
LayerState createConstantState(LayerState state, Resources res) {
@@ -256,8 +254,8 @@ public class LayerDrawable extends Drawable implements Drawable.Callback {
a.recycle();
}
- final ChildDrawable[] array = mLayerState.mChildren;
- final int N = mLayerState.mNum;
+ final ChildDrawable[] array = state.mChildren;
+ final int N = state.mNum;
for (int i = 0; i < N; i++) {
final ChildDrawable layer = array[i];
if (layer.mThemeAttrs != null) {
@@ -279,25 +277,7 @@ public class LayerDrawable extends Drawable implements Drawable.Callback {
@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++) {
- final ChildDrawable layer = array[i];
- if (layer.mThemeAttrs != null || layer.mDrawable.canApplyTheme()) {
- return true;
- }
- }
-
- return false;
+ return (mLayerState != null && mLayerState.canApplyTheme()) || super.canApplyTheme();
}
/**
@@ -940,6 +920,19 @@ public class LayerDrawable extends Drawable implements Drawable.Callback {
return this;
}
+ /**
+ * @hide
+ */
+ public void clearMutated() {
+ super.clearMutated();
+ final ChildDrawable[] array = mLayerState.mChildren;
+ final int N = mLayerState.mNum;
+ for (int i = 0; i < N; i++) {
+ array[i].mDrawable.clearMutated();
+ }
+ mMutated = false;
+ }
+
/** @hide */
@Override
public void setLayoutDirection(int layoutDirection) {
@@ -1029,22 +1022,30 @@ public class LayerDrawable extends Drawable implements Drawable.Callback {
@Override
public boolean canApplyTheme() {
- return mThemeAttrs != null;
+ if (mThemeAttrs != null || super.canApplyTheme()) {
+ return true;
+ }
+
+ final ChildDrawable[] array = mChildren;
+ final int N = mNum;
+ for (int i = 0; i < N; i++) {
+ final ChildDrawable layer = array[i];
+ if (layer.mThemeAttrs != null || layer.mDrawable.canApplyTheme()) {
+ return true;
+ }
+ }
+
+ return false;
}
@Override
public Drawable newDrawable() {
- return new LayerDrawable(this, null, null);
+ return new LayerDrawable(this, null);
}
@Override
public Drawable newDrawable(Resources res) {
- return new LayerDrawable(this, res, null);
- }
-
- @Override
- public Drawable newDrawable(Resources res, Theme theme) {
- return new LayerDrawable(this, res, theme);
+ return new LayerDrawable(this, res);
}
@Override
@@ -1106,6 +1107,20 @@ public class LayerDrawable extends Drawable implements Drawable.Callback {
mHaveOpacity = false;
mHaveIsStateful = false;
}
+
+ @Override
+ public int addAtlasableBitmaps(Collection<Bitmap> atlasList) {
+ final ChildDrawable[] array = mChildren;
+ final int N = mNum;
+ int pixelCount = 0;
+ for (int i = 0; i < N; i++) {
+ final ConstantState state = array[i].mDrawable.getConstantState();
+ if (state != null) {
+ pixelCount += state.addAtlasableBitmaps(atlasList);
+ }
+ }
+ return pixelCount;
+ }
}
}
diff --git a/graphics/java/android/graphics/drawable/LevelListDrawable.java b/graphics/java/android/graphics/drawable/LevelListDrawable.java
index bc1c61d..b01c643 100644
--- a/graphics/java/android/graphics/drawable/LevelListDrawable.java
+++ b/graphics/java/android/graphics/drawable/LevelListDrawable.java
@@ -21,6 +21,7 @@ import java.io.IOException;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
+import android.annotation.NonNull;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.Resources.Theme;
@@ -57,7 +58,7 @@ import android.util.AttributeSet;
* @attr ref android.R.styleable#LevelListDrawableItem_drawable
*/
public class LevelListDrawable extends DrawableContainer {
- private final LevelListState mLevelListState;
+ private LevelListState mLevelListState;
private boolean mMutated;
public LevelListDrawable() {
@@ -146,13 +147,25 @@ public class LevelListDrawable extends DrawableContainer {
@Override
public Drawable mutate() {
if (!mMutated && super.mutate() == this) {
- mLevelListState.mLows = mLevelListState.mLows.clone();
- mLevelListState.mHighs = mLevelListState.mHighs.clone();
+ mLevelListState.mutate();
mMutated = true;
}
return this;
}
+ @Override
+ LevelListState cloneConstantState() {
+ return new LevelListState(mLevelListState, this, null);
+ }
+
+ /**
+ * @hide
+ */
+ public void clearMutated() {
+ super.clearMutated();
+ mMutated = false;
+ }
+
private final static class LevelListState extends DrawableContainerState {
private int[] mLows;
private int[] mHighs;
@@ -161,6 +174,7 @@ public class LevelListDrawable extends DrawableContainer {
super(orig, owner, res);
if (orig != null) {
+ // Perform a shallow copy and rely on mutate() to deep-copy.
mLows = orig.mLows;
mHighs = orig.mHighs;
} else {
@@ -169,6 +183,11 @@ public class LevelListDrawable extends DrawableContainer {
}
}
+ private void mutate() {
+ mLows = mLows.clone();
+ mHighs = mHighs.clone();
+ }
+
public void addLevel(int low, int high, Drawable drawable) {
int pos = addChild(drawable);
mLows[pos] = low;
@@ -209,9 +228,17 @@ public class LevelListDrawable extends DrawableContainer {
}
}
+ @Override
+ protected void setConstantState(@NonNull DrawableContainerState state) {
+ super.setConstantState(state);
+
+ if (state instanceof LevelListState) {
+ mLevelListState = (LevelListState) state;
+ }
+ }
+
private LevelListDrawable(LevelListState state, Resources res) {
- LevelListState as = new LevelListState(state, this, res);
- mLevelListState = as;
+ final LevelListState as = new LevelListState(state, this, res);
setConstantState(as);
onLevelChange(getLevel());
}
diff --git a/graphics/java/android/graphics/drawable/NinePatchDrawable.java b/graphics/java/android/graphics/drawable/NinePatchDrawable.java
index 6c62ccf..b87ae92 100644
--- a/graphics/java/android/graphics/drawable/NinePatchDrawable.java
+++ b/graphics/java/android/graphics/drawable/NinePatchDrawable.java
@@ -48,6 +48,7 @@ import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.io.InputStream;
+import java.util.Collection;
/**
*
@@ -90,7 +91,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, null);
+ this(new NinePatchState(new NinePatch(bitmap, chunk, srcName), padding), null);
}
/**
@@ -99,7 +100,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, null);
+ this(new NinePatchState(new NinePatch(bitmap, chunk, srcName), padding), res);
mNinePatchState.mTargetDensity = mTargetDensity;
}
@@ -112,7 +113,7 @@ 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, null);
+ res);
mNinePatchState.mTargetDensity = mTargetDensity;
}
@@ -123,7 +124,7 @@ public class NinePatchDrawable extends Drawable {
*/
@Deprecated
public NinePatchDrawable(NinePatch patch) {
- this(new NinePatchState(patch, new Rect()), null, null);
+ this(new NinePatchState(patch, new Rect()), null);
}
/**
@@ -131,7 +132,7 @@ 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, null);
+ this(new NinePatchState(patch, new Rect()), res);
mNinePatchState.mTargetDensity = mTargetDensity;
}
@@ -289,7 +290,7 @@ public class NinePatchDrawable extends Drawable {
if (bounds.isEmpty()) return;
if (mNinePatchState != null) {
- NinePatch.InsetStruct insets = mNinePatchState.getBitmap().getNinePatchInsets();
+ NinePatch.InsetStruct insets = mNinePatchState.mNinePatch.getBitmap().getNinePatchInsets();
if (insets != null) {
final Rect outlineInsets = insets.outlineRect;
outline.setRoundRect(bounds.left + outlineInsets.left,
@@ -563,6 +564,14 @@ public class NinePatchDrawable extends Drawable {
return this;
}
+ /**
+ * @hide
+ */
+ public void clearMutated() {
+ super.clearMutated();
+ mMutated = false;
+ }
+
@Override
protected boolean onStateChange(int[] stateSet) {
final NinePatchState state = mNinePatchState;
@@ -640,23 +649,22 @@ public class NinePatchDrawable extends Drawable {
}
@Override
- public Bitmap getBitmap() {
- return mNinePatch.getBitmap();
+ public int addAtlasableBitmaps(Collection<Bitmap> atlasList) {
+ final Bitmap bitmap = mNinePatch.getBitmap();
+ if (isAtlasable(bitmap) && atlasList.add(bitmap)) {
+ return bitmap.getWidth() * bitmap.getHeight();
+ }
+ return 0;
}
@Override
public Drawable newDrawable() {
- return new NinePatchDrawable(this, null, null);
+ return new NinePatchDrawable(this, null);
}
@Override
public Drawable newDrawable(Resources res) {
- return new NinePatchDrawable(this, res, null);
- }
-
- @Override
- public Drawable newDrawable(Resources res, Theme theme) {
- return new NinePatchDrawable(this, res, theme);
+ return new NinePatchDrawable(this, res);
}
@Override
@@ -669,16 +677,10 @@ public class NinePatchDrawable extends Drawable {
* The one constructor to rule them all. This is called by all public
* constructors to set the state and initialize local properties.
*/
- private NinePatchDrawable(NinePatchState state, Resources res, Theme theme) {
- if (theme != null && state.canApplyTheme()) {
- // If we need to apply a theme, implicitly mutate.
- mNinePatchState = new NinePatchState(state);
- applyTheme(theme);
- } else {
- mNinePatchState = state;
- }
+ private NinePatchDrawable(NinePatchState state, Resources res) {
+ mNinePatchState = state;
- initializeWithState(state, res);
+ initializeWithState(mNinePatchState, res);
}
/**
diff --git a/graphics/java/android/graphics/drawable/Ripple.java b/graphics/java/android/graphics/drawable/Ripple.java
index 864e119..bb1d3cb 100644
--- a/graphics/java/android/graphics/drawable/Ripple.java
+++ b/graphics/java/android/graphics/drawable/Ripple.java
@@ -22,9 +22,7 @@ import android.animation.ObjectAnimator;
import android.animation.TimeInterpolator;
import android.graphics.Canvas;
import android.graphics.CanvasProperty;
-import android.graphics.Color;
import android.graphics.Paint;
-import android.graphics.Paint.Style;
import android.graphics.Rect;
import android.util.MathUtils;
import android.view.HardwareCanvas;
@@ -50,17 +48,12 @@ class Ripple {
// Hardware animators.
private final ArrayList<RenderNodeAnimator> mRunningAnimations =
new ArrayList<RenderNodeAnimator>();
- private final ArrayList<RenderNodeAnimator> mPendingAnimations =
- new ArrayList<RenderNodeAnimator>();
private final RippleDrawable mOwner;
/** Bounds used for computing max radius. */
private final Rect mBounds;
- /** Full-opacity color for drawing this ripple. */
- private int mColorOpaque;
-
/** Maximum ripple radius. */
private float mOuterRadius;
@@ -109,6 +102,10 @@ class Ripple {
/** Whether we were canceled externally and should avoid self-removal. */
private boolean mCanceled;
+ private boolean mHasPendingHardwareExit;
+ private int mPendingRadiusDuration;
+ private int mPendingOpacityDuration;
+
/**
* Creates a new ripple.
*/
@@ -120,9 +117,7 @@ class Ripple {
mStartingY = startingY;
}
- public void setup(int maxRadius, int color, float density) {
- mColorOpaque = color | 0xFF000000;
-
+ public void setup(int maxRadius, float density) {
if (maxRadius != RippleDrawable.RADIUS_AUTO) {
mHasMaxRadius = true;
mOuterRadius = maxRadius;
@@ -224,8 +219,8 @@ class Ripple {
mCanUseHardware = canUseHardware;
final boolean hasContent;
- if (canUseHardware && mHardwareAnimating) {
- hasContent = drawHardware((HardwareCanvas) c);
+ if (canUseHardware && (mHardwareAnimating || mHasPendingHardwareExit)) {
+ hasContent = drawHardware((HardwareCanvas) c, p);
} else {
hasContent = drawSoftware(c, p);
}
@@ -233,24 +228,10 @@ class Ripple {
return hasContent;
}
- private boolean drawHardware(HardwareCanvas c) {
- // If we have any pending hardware animations, cancel any running
- // animations and start those now.
- final ArrayList<RenderNodeAnimator> pendingAnimations = mPendingAnimations;
- final int N = pendingAnimations.size();
- if (N > 0) {
+ private boolean drawHardware(HardwareCanvas c, Paint p) {
+ if (mHasPendingHardwareExit) {
cancelHardwareAnimations(false);
-
- // We canceled old animations, but we're about to run new ones.
- mHardwareAnimating = true;
-
- for (int i = 0; i < N; i++) {
- pendingAnimations.get(i).setTarget(c);
- pendingAnimations.get(i).start();
- }
-
- mRunningAnimations.addAll(pendingAnimations);
- pendingAnimations.clear();
+ startPendingHardwareExit(c, p);
}
c.drawCircle(mPropX, mPropY, mPropRadius, mPropPaint);
@@ -261,8 +242,8 @@ class Ripple {
private boolean drawSoftware(Canvas c, Paint p) {
boolean hasContent = false;
- p.setColor(mColorOpaque);
- final int alpha = (int) (255 * mOpacity + 0.5f);
+ final int paintAlpha = p.getAlpha();
+ final int alpha = (int) (paintAlpha * mOpacity + 0.5f);
final float radius = MathUtils.lerp(0, mOuterRadius, mTweenRadius);
if (alpha > 0 && radius > 0) {
final float x = MathUtils.lerp(
@@ -270,8 +251,8 @@ class Ripple {
final float y = MathUtils.lerp(
mClampedStartingY - mBounds.exactCenterY(), mOuterY, mTweenY);
p.setAlpha(alpha);
- p.setStyle(Style.FILL);
c.drawCircle(x, y, radius, p);
+ p.setAlpha(paintAlpha);
hasContent = true;
}
@@ -342,8 +323,6 @@ class Ripple {
* Starts the exit animation.
*/
public void exit() {
- cancel();
-
final float radius = MathUtils.lerp(0, mOuterRadius, mTweenRadius);
final float remaining;
if (mAnimRadius != null && mAnimRadius.isRunning()) {
@@ -352,19 +331,33 @@ class Ripple {
remaining = mOuterRadius;
}
+ cancel();
+
final int radiusDuration = (int) (1000 * Math.sqrt(remaining / (WAVE_TOUCH_UP_ACCELERATION
+ WAVE_TOUCH_DOWN_ACCELERATION) * mDensity) + 0.5);
final int opacityDuration = (int) (1000 * mOpacity / WAVE_OPACITY_DECAY_VELOCITY + 0.5f);
if (mCanUseHardware) {
- exitHardware(radiusDuration, opacityDuration);
+ createPendingHardwareExit(radiusDuration, opacityDuration);
} else {
exitSoftware(radiusDuration, opacityDuration);
}
}
- private void exitHardware(int radiusDuration, int opacityDuration) {
- mPendingAnimations.clear();
+ private void createPendingHardwareExit(int radiusDuration, int opacityDuration) {
+ mHasPendingHardwareExit = true;
+ mPendingRadiusDuration = radiusDuration;
+ mPendingOpacityDuration = opacityDuration;
+
+ // The animation will start on the next draw().
+ invalidateSelf();
+ }
+
+ private void startPendingHardwareExit(HardwareCanvas c, Paint p) {
+ mHasPendingHardwareExit = false;
+
+ final int radiusDuration = mPendingRadiusDuration;
+ final int opacityDuration = mPendingOpacityDuration;
final float startX = MathUtils.lerp(
mClampedStartingX - mBounds.exactCenterX(), mOuterX, mTweenX);
@@ -372,11 +365,8 @@ class Ripple {
mClampedStartingY - mBounds.exactCenterY(), mOuterY, mTweenY);
final float startRadius = MathUtils.lerp(0, mOuterRadius, mTweenRadius);
- final Paint paint = getTempPaint();
- paint.setAntiAlias(true);
- paint.setColor(mColorOpaque);
- paint.setAlpha((int) (255 * mOpacity + 0.5f));
- paint.setStyle(Style.FILL);
+ final Paint paint = getTempPaint(p);
+ paint.setAlpha((int) (paint.getAlpha() * mOpacity + 0.5f));
mPropPaint = CanvasProperty.createPaint(paint);
mPropRadius = CanvasProperty.createFloat(startRadius);
mPropX = CanvasProperty.createFloat(startX);
@@ -385,25 +375,33 @@ class Ripple {
final RenderNodeAnimator radiusAnim = new RenderNodeAnimator(mPropRadius, mOuterRadius);
radiusAnim.setDuration(radiusDuration);
radiusAnim.setInterpolator(DECEL_INTERPOLATOR);
+ radiusAnim.setTarget(c);
+ radiusAnim.start();
final RenderNodeAnimator xAnim = new RenderNodeAnimator(mPropX, mOuterX);
xAnim.setDuration(radiusDuration);
xAnim.setInterpolator(DECEL_INTERPOLATOR);
+ xAnim.setTarget(c);
+ xAnim.start();
final RenderNodeAnimator yAnim = new RenderNodeAnimator(mPropY, mOuterY);
yAnim.setDuration(radiusDuration);
yAnim.setInterpolator(DECEL_INTERPOLATOR);
+ yAnim.setTarget(c);
+ yAnim.start();
final RenderNodeAnimator opacityAnim = new RenderNodeAnimator(mPropPaint,
RenderNodeAnimator.PAINT_ALPHA, 0);
opacityAnim.setDuration(opacityDuration);
opacityAnim.setInterpolator(LINEAR_INTERPOLATOR);
opacityAnim.addListener(mAnimationListener);
+ opacityAnim.setTarget(c);
+ opacityAnim.start();
- mPendingAnimations.add(radiusAnim);
- mPendingAnimations.add(opacityAnim);
- mPendingAnimations.add(xAnim);
- mPendingAnimations.add(yAnim);
+ mRunningAnimations.add(radiusAnim);
+ mRunningAnimations.add(opacityAnim);
+ mRunningAnimations.add(xAnim);
+ mRunningAnimations.add(yAnim);
mHardwareAnimating = true;
@@ -412,8 +410,6 @@ class Ripple {
mTweenX = 1;
mTweenY = 1;
mTweenRadius = 1;
-
- invalidateSelf();
}
/**
@@ -449,10 +445,11 @@ class Ripple {
}
}
- private Paint getTempPaint() {
+ private Paint getTempPaint(Paint original) {
if (mTempPaint == null) {
mTempPaint = new Paint();
}
+ mTempPaint.set(original);
return mTempPaint;
}
@@ -496,7 +493,7 @@ class Ripple {
public void cancel() {
mCanceled = true;
cancelSoftwareAnimations();
- cancelHardwareAnimations(true);
+ cancelHardwareAnimations(false);
mCanceled = false;
}
@@ -525,16 +522,28 @@ class Ripple {
/**
* Cancels any running hardware animations.
*/
- private void cancelHardwareAnimations(boolean cancelPending) {
+ private void cancelHardwareAnimations(boolean jumpToEnd) {
final ArrayList<RenderNodeAnimator> runningAnimations = mRunningAnimations;
final int N = runningAnimations.size();
for (int i = 0; i < N; i++) {
- runningAnimations.get(i).cancel();
+ if (jumpToEnd) {
+ runningAnimations.get(i).end();
+ } else {
+ runningAnimations.get(i).cancel();
+ }
}
runningAnimations.clear();
- if (cancelPending && !mPendingAnimations.isEmpty()) {
- mPendingAnimations.clear();
+ if (mHasPendingHardwareExit) {
+ // If we had a pending hardware exit, jump to the end state.
+ mHasPendingHardwareExit = false;
+
+ if (jumpToEnd) {
+ mOpacity = 0;
+ mTweenX = 1;
+ mTweenY = 1;
+ mTweenRadius = 1;
+ }
}
mHardwareAnimating = false;
diff --git a/graphics/java/android/graphics/drawable/RippleBackground.java b/graphics/java/android/graphics/drawable/RippleBackground.java
index faa89bf..fae4902 100644
--- a/graphics/java/android/graphics/drawable/RippleBackground.java
+++ b/graphics/java/android/graphics/drawable/RippleBackground.java
@@ -24,7 +24,6 @@ import android.graphics.Canvas;
import android.graphics.CanvasProperty;
import android.graphics.Color;
import android.graphics.Paint;
-import android.graphics.Paint.Style;
import android.graphics.Rect;
import android.util.MathUtils;
import android.view.HardwareCanvas;
@@ -43,26 +42,23 @@ class RippleBackground {
private static final float WAVE_OPACITY_DECAY_VELOCITY = 3.0f / GLOBAL_SPEED;
private static final float WAVE_OUTER_OPACITY_EXIT_VELOCITY_MAX = 4.5f * GLOBAL_SPEED;
private static final float WAVE_OUTER_OPACITY_EXIT_VELOCITY_MIN = 1.5f * GLOBAL_SPEED;
- private static final float WAVE_OUTER_OPACITY_ENTER_VELOCITY = 10.0f * GLOBAL_SPEED;
private static final float WAVE_OUTER_SIZE_INFLUENCE_MAX = 200f;
private static final float WAVE_OUTER_SIZE_INFLUENCE_MIN = 40f;
+ private static final int ENTER_DURATION = 667;
+ private static final int ENTER_DURATION_FAST = 100;
+
// Hardware animators.
private final ArrayList<RenderNodeAnimator> mRunningAnimations =
new ArrayList<RenderNodeAnimator>();
- private final ArrayList<RenderNodeAnimator> mPendingAnimations =
- new ArrayList<RenderNodeAnimator>();
private final RippleDrawable mOwner;
/** Bounds used for computing max radius. */
private final Rect mBounds;
- /** Full-opacity color for drawing this ripple. */
- private int mColorOpaque;
-
- /** Maximum alpha value for drawing this ripple. */
- private int mColorAlpha;
+ /** ARGB color for drawing this ripple. */
+ private int mColor;
/** Maximum ripple radius. */
private float mOuterRadius;
@@ -96,6 +92,11 @@ class RippleBackground {
/** Whether we have an explicit maximum radius. */
private boolean mHasMaxRadius;
+ private boolean mHasPendingHardwareExit;
+ private int mPendingOpacityDuration;
+ private int mPendingInflectionDuration;
+ private int mPendingInflectionOpacity;
+
/**
* Creates a new ripple.
*/
@@ -104,10 +105,7 @@ class RippleBackground {
mBounds = bounds;
}
- public void setup(int maxRadius, int color, float density) {
- mColorOpaque = color | 0xFF000000;
- mColorAlpha = Color.alpha(color) / 2;
-
+ public void setup(int maxRadius, float density) {
if (maxRadius != RippleDrawable.RADIUS_AUTO) {
mHasMaxRadius = true;
mOuterRadius = maxRadius;
@@ -122,10 +120,6 @@ class RippleBackground {
mDensity = density;
}
- public boolean isHardwareAnimating() {
- return mHardwareAnimating;
- }
-
public void onHotspotBoundsChanged() {
if (!mHasMaxRadius) {
final float halfWidth = mBounds.width() / 2.0f;
@@ -149,6 +143,8 @@ class RippleBackground {
* Draws the ripple centered at (0,0) using the specified paint.
*/
public boolean draw(Canvas c, Paint p) {
+ mColor = p.getColor();
+
final boolean canUseHardware = c.isHardwareAccelerated();
if (mCanUseHardware != canUseHardware && mCanUseHardware) {
// We've switched from hardware to non-hardware mode. Panic.
@@ -157,8 +153,8 @@ class RippleBackground {
mCanUseHardware = canUseHardware;
final boolean hasContent;
- if (canUseHardware && mHardwareAnimating) {
- hasContent = drawHardware((HardwareCanvas) c);
+ if (canUseHardware && (mHardwareAnimating || mHasPendingHardwareExit)) {
+ hasContent = drawHardware((HardwareCanvas) c, p);
} else {
hasContent = drawSoftware(c, p);
}
@@ -167,28 +163,13 @@ class RippleBackground {
}
public boolean shouldDraw() {
- final int outerAlpha = (int) (mColorAlpha * mOuterOpacity + 0.5f);
- return mCanUseHardware && mHardwareAnimating || outerAlpha > 0 && mOuterRadius > 0;
+ return (mCanUseHardware && mHardwareAnimating) || (mOuterOpacity > 0 && mOuterRadius > 0);
}
- private boolean drawHardware(HardwareCanvas c) {
- // If we have any pending hardware animations, cancel any running
- // animations and start those now.
- final ArrayList<RenderNodeAnimator> pendingAnimations = mPendingAnimations;
- final int N = pendingAnimations.size();
- if (N > 0) {
+ private boolean drawHardware(HardwareCanvas c, Paint p) {
+ if (mHasPendingHardwareExit) {
cancelHardwareAnimations(false);
-
- // We canceled old animations, but we're about to run new ones.
- mHardwareAnimating = true;
-
- for (int i = 0; i < N; i++) {
- pendingAnimations.get(i).setTarget(c);
- pendingAnimations.get(i).start();
- }
-
- mRunningAnimations.addAll(pendingAnimations);
- pendingAnimations.clear();
+ startPendingHardwareExit(c, p);
}
c.drawCircle(mPropOuterX, mPropOuterY, mPropOuterRadius, mPropOuterPaint);
@@ -199,12 +180,13 @@ class RippleBackground {
private boolean drawSoftware(Canvas c, Paint p) {
boolean hasContent = false;
- p.setColor(mColorOpaque);
- final int outerAlpha = (int) (mColorAlpha * mOuterOpacity + 0.5f);
- if (outerAlpha > 0 && mOuterRadius > 0) {
- p.setAlpha(outerAlpha);
- p.setStyle(Style.FILL);
- c.drawCircle(mOuterX, mOuterY, mOuterRadius, p);
+ final int paintAlpha = p.getAlpha();
+ final int alpha = (int) (paintAlpha * mOuterOpacity + 0.5f);
+ final float radius = mOuterRadius;
+ if (alpha > 0 && radius > 0) {
+ p.setAlpha(alpha);
+ c.drawCircle(mOuterX, mOuterY, radius, p);
+ p.setAlpha(paintAlpha);
hasContent = true;
}
@@ -224,21 +206,20 @@ class RippleBackground {
/**
* Starts the enter animation.
*/
- public void enter() {
+ public void enter(boolean fast) {
cancel();
- final int outerDuration = (int) (1000 * 1.0f / WAVE_OUTER_OPACITY_ENTER_VELOCITY);
- final ObjectAnimator outer = ObjectAnimator.ofFloat(this, "outerOpacity", 0, 1);
- outer.setAutoCancel(true);
- outer.setDuration(outerDuration);
- outer.setInterpolator(LINEAR_INTERPOLATOR);
+ final ObjectAnimator opacity = ObjectAnimator.ofFloat(this, "outerOpacity", 0, 1);
+ opacity.setAutoCancel(true);
+ opacity.setDuration(fast ? ENTER_DURATION_FAST : ENTER_DURATION);
+ opacity.setInterpolator(LINEAR_INTERPOLATOR);
- mAnimOuterOpacity = outer;
+ mAnimOuterOpacity = opacity;
// Enter animations always run on the UI thread, since it's unlikely
// that anything interesting is happening until the user lifts their
// finger.
- outer.start();
+ opacity.start();
}
/**
@@ -261,24 +242,36 @@ class RippleBackground {
// outer(t) = mOuterOpacity + t * WAVE_OUTER_OPACITY_VELOCITY / 1000
final int inflectionDuration = Math.max(0, (int) (1000 * (1 - mOuterOpacity)
/ (WAVE_OPACITY_DECAY_VELOCITY + outerOpacityVelocity) + 0.5f));
- final int inflectionOpacity = (int) (mColorAlpha * (mOuterOpacity
+ final int inflectionOpacity = (int) (Color.alpha(mColor) * (mOuterOpacity
+ inflectionDuration * outerOpacityVelocity * outerSizeInfluence / 1000) + 0.5f);
if (mCanUseHardware) {
- exitHardware(opacityDuration, inflectionDuration, inflectionOpacity);
+ createPendingHardwareExit(opacityDuration, inflectionDuration, inflectionOpacity);
} else {
exitSoftware(opacityDuration, inflectionDuration, inflectionOpacity);
}
}
- private void exitHardware(int opacityDuration, int inflectionDuration, int inflectionOpacity) {
- mPendingAnimations.clear();
+ private void createPendingHardwareExit(
+ int opacityDuration, int inflectionDuration, int inflectionOpacity) {
+ mHasPendingHardwareExit = true;
+ mPendingOpacityDuration = opacityDuration;
+ mPendingInflectionDuration = inflectionDuration;
+ mPendingInflectionOpacity = inflectionOpacity;
+
+ // The animation will start on the next draw().
+ invalidateSelf();
+ }
+
+ private void startPendingHardwareExit(HardwareCanvas c, Paint p) {
+ mHasPendingHardwareExit = false;
+
+ final int opacityDuration = mPendingOpacityDuration;
+ final int inflectionDuration = mPendingInflectionDuration;
+ final int inflectionOpacity = mPendingInflectionOpacity;
- final Paint outerPaint = getTempPaint();
- outerPaint.setAntiAlias(true);
- outerPaint.setColor(mColorOpaque);
- outerPaint.setAlpha((int) (mColorAlpha * mOuterOpacity + 0.5f));
- outerPaint.setStyle(Style.FILL);
+ final Paint outerPaint = getTempPaint(p);
+ outerPaint.setAlpha((int) (outerPaint.getAlpha() * mOuterOpacity + 0.5f));
mPropOuterPaint = CanvasProperty.createPaint(outerPaint);
mPropOuterRadius = CanvasProperty.createFloat(mOuterRadius);
mPropOuterX = CanvasProperty.createFloat(mOuterX);
@@ -302,8 +295,10 @@ class RippleBackground {
outerFadeOutAnim.setStartDelay(inflectionDuration);
outerFadeOutAnim.setStartValue(inflectionOpacity);
outerFadeOutAnim.addListener(mAnimationListener);
+ outerFadeOutAnim.setTarget(c);
+ outerFadeOutAnim.start();
- mPendingAnimations.add(outerFadeOutAnim);
+ mRunningAnimations.add(outerFadeOutAnim);
} else {
outerOpacityAnim.addListener(mAnimationListener);
}
@@ -315,14 +310,15 @@ class RippleBackground {
outerOpacityAnim.addListener(mAnimationListener);
}
- mPendingAnimations.add(outerOpacityAnim);
+ outerOpacityAnim.setTarget(c);
+ outerOpacityAnim.start();
+
+ mRunningAnimations.add(outerOpacityAnim);
mHardwareAnimating = true;
// Set up the software values to match the hardware end values.
mOuterOpacity = 0;
-
- invalidateSelf();
}
/**
@@ -341,10 +337,11 @@ class RippleBackground {
}
}
- private Paint getTempPaint() {
+ private Paint getTempPaint(Paint original) {
if (mTempPaint == null) {
mTempPaint = new Paint();
}
+ mTempPaint.set(original);
return mTempPaint;
}
@@ -402,7 +399,7 @@ class RippleBackground {
*/
public void cancel() {
cancelSoftwareAnimations();
- cancelHardwareAnimations(true);
+ cancelHardwareAnimations(false);
}
private void cancelSoftwareAnimations() {
@@ -415,16 +412,25 @@ class RippleBackground {
/**
* Cancels any running hardware animations.
*/
- private void cancelHardwareAnimations(boolean cancelPending) {
+ private void cancelHardwareAnimations(boolean jumpToEnd) {
final ArrayList<RenderNodeAnimator> runningAnimations = mRunningAnimations;
final int N = runningAnimations.size();
for (int i = 0; i < N; i++) {
- runningAnimations.get(i).cancel();
+ if (jumpToEnd) {
+ runningAnimations.get(i).end();
+ } else {
+ runningAnimations.get(i).cancel();
+ }
}
runningAnimations.clear();
- if (cancelPending && !mPendingAnimations.isEmpty()) {
- mPendingAnimations.clear();
+ if (mHasPendingHardwareExit) {
+ // If we had a pending hardware exit, jump to the end state.
+ mHasPendingHardwareExit = false;
+
+ if (jumpToEnd) {
+ mOuterOpacity = 0;
+ }
}
mHardwareAnimating = false;
diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java
index c7aa98e..1263447 100644
--- a/graphics/java/android/graphics/drawable/RippleDrawable.java
+++ b/graphics/java/android/graphics/drawable/RippleDrawable.java
@@ -16,29 +16,33 @@
package android.graphics.drawable;
+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.ColorStateList;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
+import android.graphics.Matrix;
import android.graphics.Outline;
import android.graphics.Paint;
import android.graphics.PixelFormat;
-import android.graphics.PorterDuff.Mode;
-import android.graphics.PorterDuffXfermode;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
import android.graphics.Rect;
+import android.graphics.Shader;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
-import com.android.internal.R;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
import java.io.IOException;
import java.util.Arrays;
@@ -56,7 +60,7 @@ import java.util.Arrays;
* &ltripple android:color="#ffff0000">
* &ltitem android:id="@android:id/mask"
* android:drawable="@android:color/white" />
- * &ltripple /></code>
+ * &lt/ripple></code>
* </pre>
* <p>
* If a mask layer is set, the ripple effect will be masked against that layer
@@ -65,15 +69,15 @@ import java.util.Arrays;
* If no mask layer is set, the ripple effect is masked against the composite
* of the child layers.
* <pre>
- * <code>&lt!-- A blue ripple drawn atop a black rectangle. --/>
+ * <code>&lt!-- A green ripple drawn atop a black rectangle. --/>
* &ltripple android:color="#ff00ff00">
* &ltitem android:drawable="@android:color/black" />
- * &ltripple />
+ * &lt/ripple>
*
- * &lt!-- A red ripple drawn atop a drawable resource. --/>
- * &ltripple android:color="#ff00ff00">
+ * &lt!-- A blue ripple drawn atop a drawable resource. --/>
+ * &ltripple android:color="#ff0000ff">
* &ltitem android:drawable="@drawable/my_drawable" />
- * &ltripple /></code>
+ * &lt/ripple></code>
* </pre>
* <p>
* If no child layers or mask is specified and the ripple is set as a View
@@ -81,16 +85,17 @@ import java.util.Arrays;
* background within the View's hierarchy. In this case, the drawing region
* may extend outside of the Drawable bounds.
* <pre>
- * <code>&lt!-- An unbounded green ripple. --/>
- * &ltripple android:color="#ff0000ff" /></code>
+ * <code>&lt!-- An unbounded red ripple. --/>
+ * &ltripple android:color="#ffff0000" /></code>
* </pre>
*
* @attr ref android.R.styleable#RippleDrawable_color
*/
public class RippleDrawable extends LayerDrawable {
- private static final PorterDuffXfermode DST_IN = new PorterDuffXfermode(Mode.DST_IN);
- private static final PorterDuffXfermode SRC_ATOP = new PorterDuffXfermode(Mode.SRC_ATOP);
- private static final PorterDuffXfermode SRC_OVER = new PorterDuffXfermode(Mode.SRC_OVER);
+ private static final int MASK_UNKNOWN = -1;
+ private static final int MASK_NONE = 0;
+ private static final int MASK_CONTENT = 1;
+ private static final int MASK_EXPLICIT = 2;
/**
* Constant for automatically determining the maximum ripple radius.
@@ -123,6 +128,13 @@ public class RippleDrawable extends LayerDrawable {
/** The current background. May be actively animating or pending entry. */
private RippleBackground mBackground;
+ private Bitmap mMaskBuffer;
+ private BitmapShader mMaskShader;
+ private Canvas mMaskCanvas;
+ private Matrix mMaskMatrix;
+ private PorterDuffColorFilter mMaskColorFilter;
+ private boolean mHasValidMask;
+
/** Whether we expect to draw a background when visible. */
private boolean mBackgroundActive;
@@ -147,9 +159,6 @@ public class RippleDrawable extends LayerDrawable {
/** Paint used to control appearance of ripples. */
private Paint mRipplePaint;
- /** Paint used to control reveal layer masking. */
- private Paint mMaskingPaint;
-
/** Target density of the display into which ripples are drawn. */
private float mDensity = 1.0f;
@@ -157,17 +166,10 @@ public class RippleDrawable extends LayerDrawable {
private boolean mOverrideBounds;
/**
- * Whether the next draw MUST draw something to canvas. Used to work around
- * a bug in hardware invalidation following a render thread-accelerated
- * animation.
- */
- private boolean mNeedsDraw;
-
- /**
* Constructor used for drawable inflation.
*/
RippleDrawable() {
- this(new RippleState(null, null, null), null, null);
+ this(new RippleState(null, null, null), null);
}
/**
@@ -180,7 +182,7 @@ public class RippleDrawable extends LayerDrawable {
*/
public RippleDrawable(@NonNull ColorStateList color, @Nullable Drawable content,
@Nullable Drawable mask) {
- this(new RippleState(null, null, null), null, null);
+ this(new RippleState(null, null, null), null);
if (color == null) {
throw new IllegalArgumentException("RippleDrawable requires a non-null color");
@@ -203,21 +205,15 @@ public class RippleDrawable extends LayerDrawable {
public void jumpToCurrentState() {
super.jumpToCurrentState();
- boolean needsDraw = false;
-
if (mRipple != null) {
- needsDraw |= mRipple.isHardwareAnimating();
mRipple.jump();
}
if (mBackground != null) {
- needsDraw |= mBackground.isHardwareAnimating();
mBackground.jump();
}
- needsDraw |= cancelExitingRipples();
-
- mNeedsDraw = needsDraw;
+ cancelExitingRipples();
invalidateSelf();
}
@@ -280,7 +276,7 @@ public class RippleDrawable extends LayerDrawable {
}
setRippleActive(enabled && pressed);
- setBackgroundActive(focused || (enabled && pressed));
+ setBackgroundActive(focused || (enabled && pressed), focused);
return changed;
}
@@ -296,11 +292,11 @@ public class RippleDrawable extends LayerDrawable {
}
}
- private void setBackgroundActive(boolean active) {
+ private void setBackgroundActive(boolean active, boolean focused) {
if (mBackgroundActive != active) {
mBackgroundActive = active;
if (active) {
- tryBackgroundEnter();
+ tryBackgroundEnter(focused);
} else {
tryBackgroundExit();
}
@@ -333,8 +329,11 @@ public class RippleDrawable extends LayerDrawable {
}
if (mBackgroundActive) {
- tryBackgroundEnter();
+ tryBackgroundEnter(false);
}
+
+ // Skip animations, just show the correct final states.
+ jumpToCurrentState();
}
return changed;
@@ -470,7 +469,7 @@ public class RippleDrawable extends LayerDrawable {
@Override
public boolean canApplyTheme() {
- return super.canApplyTheme() || mState != null && mState.mTouchThemeAttrs != null;
+ return (mState != null && mState.canApplyTheme()) || super.canApplyTheme();
}
@Override
@@ -489,14 +488,13 @@ public class RippleDrawable extends LayerDrawable {
/**
* Creates an active hotspot at the specified location.
*/
- private void tryBackgroundEnter() {
+ private void tryBackgroundEnter(boolean focused) {
if (mBackground == null) {
mBackground = new RippleBackground(this, mHotspotBounds);
}
- final int color = mState.mColor.getColorForState(getState(), Color.TRANSPARENT);
- mBackground.setup(mState.mMaxRadius, color, mDensity);
- mBackground.enter();
+ mBackground.setup(mState.mMaxRadius, mDensity);
+ mBackground.enter(focused);
}
private void tryBackgroundExit() {
@@ -531,8 +529,7 @@ public class RippleDrawable extends LayerDrawable {
mRipple = new Ripple(this, mHotspotBounds, x, y);
}
- final int color = mState.mColor.getColorForState(getState(), Color.TRANSPARENT);
- mRipple.setup(mState.mMaxRadius, color, mDensity);
+ mRipple.setup(mState.mMaxRadius, mDensity);
mRipple.enter();
}
@@ -556,23 +553,19 @@ public class RippleDrawable extends LayerDrawable {
* background. Nothing will be drawn after this method is called.
*/
private void clearHotspots() {
- boolean needsDraw = false;
-
if (mRipple != null) {
- needsDraw |= mRipple.isHardwareAnimating();
mRipple.cancel();
mRipple = null;
+ mRippleActive = false;
}
if (mBackground != null) {
- needsDraw |= mBackground.isHardwareAnimating();
mBackground.cancel();
mBackground = null;
+ mBackgroundActive = false;
}
- needsDraw |= cancelExitingRipples();
-
- mNeedsDraw = needsDraw;
+ cancelExitingRipples();
invalidateSelf();
}
@@ -628,57 +621,121 @@ public class RippleDrawable extends LayerDrawable {
}
}
+ /**
+ * Optimized for drawing ripples with a mask layer and optional content.
+ */
@Override
public void draw(@NonNull Canvas canvas) {
- final boolean hasMask = mMask != null;
- final boolean drawNonMaskContent = mLayerState.mNum > (hasMask ? 1 : 0);
- final boolean drawMask = hasMask && mMask.getOpacity() != PixelFormat.OPAQUE;
+ // Clip to the dirty bounds, which will be the drawable bounds if we
+ // have a mask or content and the ripple bounds if we're projecting.
final Rect bounds = getDirtyBounds();
final int saveCount = canvas.save(Canvas.CLIP_SAVE_FLAG);
canvas.clipRect(bounds);
- // If we have content, draw it into a layer first.
- final int contentLayer;
- if (drawNonMaskContent) {
- contentLayer = drawContentLayer(canvas, bounds, SRC_OVER);
- } else {
- contentLayer = -1;
+ drawContent(canvas);
+ drawBackgroundAndRipples(canvas);
+
+ canvas.restoreToCount(saveCount);
+ }
+
+ @Override
+ public void invalidateSelf() {
+ super.invalidateSelf();
+
+ // Force the mask to update on the next draw().
+ mHasValidMask = false;
+ }
+
+ /**
+ * @return whether we need to use a mask
+ */
+ private void updateMaskShaderIfNeeded() {
+ if (mHasValidMask) {
+ return;
}
- // Next, try to draw the ripples (into a layer if necessary). If we need
- // to mask against the underlying content, set the xfermode to SRC_ATOP.
- final PorterDuffXfermode xfermode = (hasMask || !drawNonMaskContent) ? SRC_OVER : SRC_ATOP;
+ final int maskType = getMaskType();
+ if (maskType == MASK_UNKNOWN) {
+ return;
+ }
+
+ mHasValidMask = true;
- // If we have a background and a non-opaque mask, draw the masking layer.
- final int backgroundLayer = drawBackgroundLayer(canvas, bounds, xfermode, drawMask);
- if (backgroundLayer >= 0) {
- if (drawMask) {
- drawMaskingLayer(canvas, bounds, DST_IN);
+ final Rect bounds = getBounds();
+ if (maskType == MASK_NONE || bounds.isEmpty()) {
+ if (mMaskBuffer != null) {
+ mMaskBuffer.recycle();
+ mMaskBuffer = null;
+ mMaskShader = null;
+ mMaskCanvas = null;
}
- canvas.restoreToCount(backgroundLayer);
+ mMaskMatrix = null;
+ mMaskColorFilter = null;
+ return;
}
- // If we have ripples and a non-opaque mask, draw the masking layer.
- final int rippleLayer = drawRippleLayer(canvas, bounds, xfermode);
- if (rippleLayer >= 0) {
- if (drawMask) {
- drawMaskingLayer(canvas, bounds, DST_IN);
+ // Ensure we have a correctly-sized buffer.
+ if (mMaskBuffer == null
+ || mMaskBuffer.getWidth() != bounds.width()
+ || mMaskBuffer.getHeight() != bounds.height()) {
+ if (mMaskBuffer != null) {
+ mMaskBuffer.recycle();
}
- canvas.restoreToCount(rippleLayer);
+
+ mMaskBuffer = Bitmap.createBitmap(
+ bounds.width(), bounds.height(), Bitmap.Config.ALPHA_8);
+ mMaskShader = new BitmapShader(mMaskBuffer,
+ Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
+ mMaskCanvas = new Canvas(mMaskBuffer);
+ } else {
+ mMaskBuffer.eraseColor(Color.TRANSPARENT);
}
- // If we failed to draw anything and we just canceled animations, at
- // least draw a color so that hardware invalidation works correctly.
- if (contentLayer < 0 && backgroundLayer < 0 && rippleLayer < 0 && mNeedsDraw) {
- canvas.drawColor(Color.TRANSPARENT);
+ if (mMaskMatrix == null) {
+ mMaskMatrix = new Matrix();
+ } else {
+ mMaskMatrix.reset();
+ }
- // Request another draw so we can avoid adding a transparent layer
- // during the next display list refresh.
- invalidateSelf();
+ if (mMaskColorFilter == null) {
+ mMaskColorFilter = new PorterDuffColorFilter(0, PorterDuff.Mode.SRC_IN);
}
- mNeedsDraw = false;
- canvas.restoreToCount(saveCount);
+ // Draw the appropriate mask.
+ if (maskType == MASK_EXPLICIT) {
+ drawMask(mMaskCanvas);
+ } else if (maskType == MASK_CONTENT) {
+ drawContent(mMaskCanvas);
+ }
+ }
+
+ private int getMaskType() {
+ if (mRipple == null && mExitingRipplesCount <= 0
+ && (mBackground == null || !mBackground.shouldDraw())) {
+ // We might need a mask later.
+ return MASK_UNKNOWN;
+ }
+
+ if (mMask != null) {
+ if (mMask.getOpacity() == PixelFormat.OPAQUE) {
+ // Clipping handles opaque explicit masks.
+ return MASK_NONE;
+ } else {
+ return MASK_EXPLICIT;
+ }
+ }
+
+ // Check for non-opaque, non-mask content.
+ final ChildDrawable[] array = mLayerState.mChildren;
+ final int count = mLayerState.mNum;
+ for (int i = 0; i < count; i++) {
+ if (array[i].mDrawable.getOpacity() != PixelFormat.OPAQUE) {
+ return MASK_CONTENT;
+ }
+ }
+
+ // Clipping handles opaque content.
+ return MASK_NONE;
}
/**
@@ -711,141 +768,92 @@ public class RippleDrawable extends LayerDrawable {
return -1;
}
- private int drawContentLayer(Canvas canvas, Rect bounds, PorterDuffXfermode mode) {
+ private void drawContent(Canvas canvas) {
+ // Draw everything except the mask.
final ChildDrawable[] array = mLayerState.mChildren;
final int count = mLayerState.mNum;
-
- // We don't need a layer if we don't expect to draw any ripples, we have
- // an explicit mask, or if the non-mask content is all opaque.
- boolean needsLayer = false;
- if ((mExitingRipplesCount > 0 || mBackground != null) && mMask == null) {
- for (int i = 0; i < count; i++) {
- if (array[i].mId != R.id.mask
- && array[i].mDrawable.getOpacity() != PixelFormat.OPAQUE) {
- needsLayer = true;
- break;
- }
- }
- }
-
- final Paint maskingPaint = getMaskingPaint(mode);
- final int restoreToCount = needsLayer ? canvas.saveLayer(bounds.left, bounds.top,
- bounds.right, bounds.bottom, maskingPaint) : -1;
-
- // Draw everything except the mask.
for (int i = 0; i < count; i++) {
if (array[i].mId != R.id.mask) {
array[i].mDrawable.draw(canvas);
}
}
-
- return restoreToCount;
}
- private int drawBackgroundLayer(
- Canvas canvas, Rect bounds, PorterDuffXfermode mode, boolean drawMask) {
- int saveCount = -1;
+ private void drawBackgroundAndRipples(Canvas canvas) {
+ final Ripple active = mRipple;
+ final RippleBackground background = mBackground;
+ final int count = mExitingRipplesCount;
+ if (active == null && count <= 0 && (background == null || !background.shouldDraw())) {
+ // Move along, nothing to draw here.
+ return;
+ }
- if (mBackground != null && mBackground.shouldDraw()) {
- // TODO: We can avoid saveLayer here if we push the xfermode into
- // the background's render thread animator at exit() time.
- if (drawMask || mode != SRC_OVER) {
- saveCount = canvas.saveLayer(bounds.left, bounds.top, bounds.right,
- bounds.bottom, getMaskingPaint(mode));
- }
+ final float x = mHotspotBounds.exactCenterX();
+ final float y = mHotspotBounds.exactCenterY();
+ canvas.translate(x, y);
- final float x = mHotspotBounds.exactCenterX();
- final float y = mHotspotBounds.exactCenterY();
- canvas.translate(x, y);
- mBackground.draw(canvas, getRipplePaint());
- canvas.translate(-x, -y);
+ updateMaskShaderIfNeeded();
+
+ // Position the shader to account for canvas translation.
+ if (mMaskShader != null) {
+ mMaskMatrix.setTranslate(-x, -y);
+ mMaskShader.setLocalMatrix(mMaskMatrix);
}
- return saveCount;
- }
+ // Grab the color for the current state and cut the alpha channel in
+ // half so that the ripple and background together yield full alpha.
+ final int color = mState.mColor.getColorForState(getState(), Color.BLACK);
+ final int halfAlpha = (Color.alpha(color) / 2) << 24;
+ final Paint p = getRipplePaint();
- private int drawRippleLayer(Canvas canvas, Rect bounds, PorterDuffXfermode mode) {
- boolean drewRipples = false;
- int restoreToCount = -1;
- int restoreTranslate = -1;
+ if (mMaskColorFilter != null) {
+ // The ripple timing depends on the paint's alpha value, so we need
+ // to push just the alpha channel into the paint and let the filter
+ // handle the full-alpha color.
+ final int fullAlphaColor = color | (0xFF << 24);
+ mMaskColorFilter.setColor(fullAlphaColor);
- // Draw ripples and update the animating ripples array.
- final int count = mExitingRipplesCount;
- final Ripple[] ripples = mExitingRipples;
- for (int i = 0; i <= count; i++) {
- final Ripple ripple;
- if (i < count) {
- ripple = ripples[i];
- } else if (mRipple != null) {
- ripple = mRipple;
- } else {
- continue;
- }
-
- // If we're masking the ripple layer, make sure we have a layer
- // first. This will merge SRC_OVER (directly) onto the canvas.
- if (restoreToCount < 0) {
- final Paint maskingPaint = getMaskingPaint(mode);
- final int color = mState.mColor.getColorForState(getState(), Color.TRANSPARENT);
- final int alpha = Color.alpha(color);
- maskingPaint.setAlpha(alpha / 2);
-
- // TODO: We can avoid saveLayer here if we're only drawing one
- // ripple and we don't have content or a translucent mask.
- restoreToCount = canvas.saveLayer(bounds.left, bounds.top,
- bounds.right, bounds.bottom, maskingPaint);
-
- // Translate the canvas to the current hotspot bounds.
- restoreTranslate = canvas.save();
- canvas.translate(mHotspotBounds.exactCenterX(), mHotspotBounds.exactCenterY());
- }
+ p.setColor(halfAlpha);
+ p.setColorFilter(mMaskColorFilter);
+ p.setShader(mMaskShader);
+ } else {
+ final int halfAlphaColor = (color & 0xFFFFFF) | halfAlpha;
+ p.setColor(halfAlphaColor);
+ p.setColorFilter(null);
+ p.setShader(null);
+ }
- drewRipples |= ripple.draw(canvas, getRipplePaint());
+ if (background != null && background.shouldDraw()) {
+ background.draw(canvas, p);
}
- // Always restore the translation.
- if (restoreTranslate >= 0) {
- canvas.restoreToCount(restoreTranslate);
+ if (count > 0) {
+ final Ripple[] ripples = mExitingRipples;
+ for (int i = 0; i < count; i++) {
+ ripples[i].draw(canvas, p);
+ }
}
- // If we created a layer with no content, merge it immediately.
- if (restoreToCount >= 0 && !drewRipples) {
- canvas.restoreToCount(restoreToCount);
- restoreToCount = -1;
+ if (active != null) {
+ active.draw(canvas, p);
}
- return restoreToCount;
+ canvas.translate(-x, -y);
}
- private int drawMaskingLayer(Canvas canvas, Rect bounds, PorterDuffXfermode mode) {
- final int restoreToCount = canvas.saveLayer(bounds.left, bounds.top,
- bounds.right, bounds.bottom, getMaskingPaint(mode));
-
- // Ensure that DST_IN blends using the entire layer.
- canvas.drawColor(Color.TRANSPARENT);
-
+ private void drawMask(Canvas canvas) {
mMask.draw(canvas);
-
- return restoreToCount;
}
private Paint getRipplePaint() {
if (mRipplePaint == null) {
mRipplePaint = new Paint();
mRipplePaint.setAntiAlias(true);
+ mRipplePaint.setStyle(Paint.Style.FILL);
}
return mRipplePaint;
}
- private Paint getMaskingPaint(PorterDuffXfermode xfermode) {
- if (mMaskingPaint == null) {
- mMaskingPaint = new Paint();
- }
- mMaskingPaint.setXfermode(xfermode);
- mMaskingPaint.setAlpha(0xFF);
- return mMaskingPaint;
- }
-
@Override
public Rect getDirtyBounds() {
if (isProjected()) {
@@ -893,6 +901,10 @@ public class RippleDrawable extends LayerDrawable {
// LayerDrawable creates a new state using createConstantState, so
// this should always be a safe cast.
mState = (RippleState) mLayerState;
+
+ // The locally cached drawable may have changed.
+ mMask = findDrawableByLayerId(R.id.mask);
+
return this;
}
@@ -924,17 +936,12 @@ public class RippleDrawable extends LayerDrawable {
@Override
public Drawable newDrawable() {
- return new RippleDrawable(this, null, null);
+ return new RippleDrawable(this, null);
}
@Override
public Drawable newDrawable(Resources res) {
- return new RippleDrawable(this, res, null);
- }
-
- @Override
- public Drawable newDrawable(Resources res, Theme theme) {
- return new RippleDrawable(this, res, theme);
+ return new RippleDrawable(this, res);
}
}
@@ -968,37 +975,18 @@ public class RippleDrawable extends LayerDrawable {
return mState.mMaxRadius;
}
- private RippleDrawable(RippleState state, Resources res, Theme theme) {
- boolean needsTheme = false;
+ private RippleDrawable(RippleState state, Resources res) {
+ mState = new RippleState(state, this, res);
+ mLayerState = mState;
- final RippleState ns;
- if (theme != null && state != null && state.canApplyTheme()) {
- ns = new RippleState(state, this, res);
- needsTheme = true;
- } else if (state == null) {
- ns = new RippleState(null, this, res);
- } else {
- // We always need a new state since child drawables contain local
- // state but live within the parent's constant state.
- // TODO: Move child drawables into local state.
- ns = new RippleState(state, this, res);
+ if (mState.mNum > 0) {
+ ensurePadding();
}
if (res != null) {
mDensity = res.getDisplayMetrics().density;
}
- mState = ns;
- mLayerState = ns;
-
- if (ns.mNum > 0) {
- ensurePadding();
- }
-
- if (needsTheme) {
- applyTheme(theme);
- }
-
initializeFromState();
}
diff --git a/graphics/java/android/graphics/drawable/RotateDrawable.java b/graphics/java/android/graphics/drawable/RotateDrawable.java
index 55c9637..e1991fe 100644
--- a/graphics/java/android/graphics/drawable/RotateDrawable.java
+++ b/graphics/java/android/graphics/drawable/RotateDrawable.java
@@ -16,6 +16,8 @@
package android.graphics.drawable;
+import com.android.internal.R;
+
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -29,7 +31,6 @@ import android.content.res.TypedArray;
import android.content.res.Resources.Theme;
import android.util.TypedValue;
import android.util.AttributeSet;
-import android.util.Log;
import java.io.IOException;
@@ -215,7 +216,7 @@ public class RotateDrawable extends Drawable implements Drawable.Callback {
* @attr ref android.R.styleable#RotateDrawable_pivotX
*/
public void setPivotX(float pivotX) {
- if (mState.mPivotX == pivotX) {
+ if (mState.mPivotX != pivotX) {
mState.mPivotX = pivotX;
invalidateSelf();
}
@@ -241,7 +242,7 @@ public class RotateDrawable extends Drawable implements Drawable.Callback {
* @see #isPivotXRelative()
*/
public void setPivotXRelative(boolean relative) {
- if (mState.mPivotXRel == relative) {
+ if (mState.mPivotXRel != relative) {
mState.mPivotXRel = relative;
invalidateSelf();
}
@@ -269,7 +270,7 @@ public class RotateDrawable extends Drawable implements Drawable.Callback {
* @attr ref android.R.styleable#RotateDrawable_pivotY
*/
public void setPivotY(float pivotY) {
- if (mState.mPivotY == pivotY) {
+ if (mState.mPivotY != pivotY) {
mState.mPivotY = pivotY;
invalidateSelf();
}
@@ -295,7 +296,7 @@ public class RotateDrawable extends Drawable implements Drawable.Callback {
* @see #isPivotYRelative()
*/
public void setPivotYRelative(boolean relative) {
- if (mState.mPivotYRel == relative) {
+ if (mState.mPivotYRel != relative) {
mState.mPivotYRel = relative;
invalidateSelf();
}
@@ -312,6 +313,11 @@ public class RotateDrawable extends Drawable implements Drawable.Callback {
}
@Override
+ public boolean canApplyTheme() {
+ return (mState != null && mState.canApplyTheme()) || super.canApplyTheme();
+ }
+
+ @Override
public void invalidateDrawable(Drawable who) {
final Callback callback = getCallback();
if (callback != null) {
@@ -399,79 +405,104 @@ public class RotateDrawable extends Drawable implements Drawable.Callback {
@Override
public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
throws XmlPullParserException, IOException {
- final TypedArray a = obtainAttributes(r, theme, attrs,
- com.android.internal.R.styleable.RotateDrawable);
-
- super.inflateWithAttributes(r, parser, a,
- com.android.internal.R.styleable.RotateDrawable_visible);
-
- TypedValue tv = a.peekValue(com.android.internal.R.styleable.RotateDrawable_pivotX);
- final boolean pivotXRel;
- final float pivotX;
- if (tv == null) {
- pivotXRel = true;
- pivotX = 0.5f;
- } else {
- pivotXRel = tv.type == TypedValue.TYPE_FRACTION;
- pivotX = pivotXRel ? tv.getFraction(1.0f, 1.0f) : tv.getFloat();
- }
+ final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.RotateDrawable);
+ super.inflateWithAttributes(r, parser, a, R.styleable.RotateDrawable_visible);
+
+ // Reset mDrawable to preserve old multiple-inflate behavior. This is
+ // silly, but we have CTS tests that rely on it.
+ mState.mDrawable = null;
+
+ updateStateFromTypedArray(a);
+ inflateChildElements(r, parser, attrs, theme);
+ verifyRequiredAttributes(a);
+ a.recycle();
+ }
+
+ @Override
+ public void applyTheme(Theme t) {
+ super.applyTheme(t);
- tv = a.peekValue(com.android.internal.R.styleable.RotateDrawable_pivotY);
- final boolean pivotYRel;
- final float pivotY;
- if (tv == null) {
- pivotYRel = true;
- pivotY = 0.5f;
- } else {
- pivotYRel = tv.type == TypedValue.TYPE_FRACTION;
- pivotY = pivotYRel ? tv.getFraction(1.0f, 1.0f) : tv.getFloat();
+ final RotateState state = mState;
+ if (state == null) {
+ return;
}
- final float fromDegrees = a.getFloat(
- com.android.internal.R.styleable.RotateDrawable_fromDegrees, 0.0f);
- final float toDegrees = a.getFloat(
- com.android.internal.R.styleable.RotateDrawable_toDegrees, 360.0f);
+ 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();
+ }
+ }
- final int res = a.getResourceId(
- com.android.internal.R.styleable.RotateDrawable_drawable, 0);
- Drawable drawable = null;
- if (res > 0) {
- drawable = r.getDrawable(res, theme);
+ if (state.mDrawable != null && state.mDrawable.canApplyTheme()) {
+ state.mDrawable.applyTheme(t);
}
- a.recycle();
+ }
- final int outerDepth = parser.getDepth();
+ private void inflateChildElements(Resources r, XmlPullParser parser, AttributeSet attrs,
+ Theme theme) throws XmlPullParserException, IOException {
+ Drawable dr = null;
int type;
- while ((type = parser.next()) != XmlPullParser.END_DOCUMENT &&
- (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
-
+ final int outerDepth = parser.getDepth();
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
if (type != XmlPullParser.START_TAG) {
continue;
}
+ dr = Drawable.createFromXmlInner(r, parser, attrs, theme);
+ }
- if ((drawable = Drawable.createFromXmlInner(r, parser, attrs, theme)) == null) {
- Log.w("drawable", "Bad element under <rotate>: "
- + parser .getName());
- }
+ if (dr != null) {
+ mState.mDrawable = dr;
+ dr.setCallback(this);
+ }
+ }
+
+ private void verifyRequiredAttributes(TypedArray a) throws XmlPullParserException {
+ // If we're not waiting on a theme, verify required attributes.
+ if (mState.mDrawable == null && (mState.mThemeAttrs == null
+ || mState.mThemeAttrs[R.styleable.ScaleDrawable_drawable] == 0)) {
+ throw new XmlPullParserException(a.getPositionDescription()
+ + ": <rotate> tag requires a 'drawable' attribute or "
+ + "child tag defining a drawable");
}
+ }
+
+ private void updateStateFromTypedArray(TypedArray a) {
+ final RotateState state = mState;
- if (drawable == null) {
- Log.w("drawable", "No drawable specified for <rotate>");
+ // Account for any configuration changes.
+ state.mChangingConfigurations |= a.getChangingConfigurations();
+
+ // Extract the theme attributes, if any.
+ state.mThemeAttrs = a.extractThemeAttrs();
+
+ if (a.hasValue(R.styleable.RotateDrawable_pivotX)) {
+ final TypedValue tv = a.peekValue(R.styleable.RotateDrawable_pivotX);
+ state.mPivotXRel = tv.type == TypedValue.TYPE_FRACTION;
+ state.mPivotX = state.mPivotXRel ? tv.getFraction(1.0f, 1.0f) : tv.getFloat();
}
- final RotateState st = mState;
- st.mDrawable = drawable;
- st.mPivotXRel = pivotXRel;
- st.mPivotX = pivotX;
- st.mPivotYRel = pivotYRel;
- st.mPivotY = pivotY;
- st.mFromDegrees = fromDegrees;
- st.mCurrentDegrees = fromDegrees;
- st.mToDegrees = toDegrees;
-
- if (drawable != null) {
- drawable.setCallback(this);
+ if (a.hasValue(R.styleable.RotateDrawable_pivotY)) {
+ final TypedValue tv = a.peekValue(R.styleable.RotateDrawable_pivotY);
+ state.mPivotYRel = tv.type == TypedValue.TYPE_FRACTION;
+ state.mPivotY = state.mPivotYRel ? tv.getFraction(1.0f, 1.0f) : tv.getFloat();
+ }
+
+ state.mFromDegrees = a.getFloat(R.styleable.RotateDrawable_fromDegrees, state.mFromDegrees);
+ 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) {
+ state.mDrawable = dr;
+ dr.setCallback(this);
}
}
@@ -485,30 +516,42 @@ public class RotateDrawable extends Drawable implements Drawable.Callback {
}
/**
+ * @hide
+ */
+ public void clearMutated() {
+ super.clearMutated();
+ mState.mDrawable.clearMutated();
+ mMutated = false;
+ }
+
+ /**
* Represents the state of a rotation for a given drawable. The same
* rotate drawable can be invoked with different states to drive several
* rotations at the same time.
*/
final static class RotateState extends Drawable.ConstantState {
- Drawable mDrawable;
-
+ int[] mThemeAttrs;
int mChangingConfigurations;
- boolean mPivotXRel;
- float mPivotX;
- boolean mPivotYRel;
- float mPivotY;
+ Drawable mDrawable;
- float mFromDegrees;
- float mToDegrees;
+ boolean mPivotXRel = true;
+ float mPivotX = 0.5f;
+ boolean mPivotYRel = true;
+ float mPivotY = 0.5f;
- float mCurrentDegrees;
+ float mFromDegrees = 0.0f;
+ float mToDegrees = 360.0f;
+
+ float mCurrentDegrees = 0.0f;
- private boolean mCanConstantState;
private boolean mCheckedConstantState;
+ private boolean mCanConstantState;
- public RotateState(RotateState orig, RotateDrawable owner, Resources res) {
+ RotateState(RotateState orig, RotateDrawable owner, Resources res) {
if (orig != null) {
+ mThemeAttrs = orig.mThemeAttrs;
+ mChangingConfigurations = orig.mChangingConfigurations;
if (res != null) {
mDrawable = orig.mDrawable.getConstantState().newDrawable(res);
} else {
@@ -522,13 +565,20 @@ public class RotateDrawable extends Drawable implements Drawable.Callback {
mPivotX = orig.mPivotX;
mPivotYRel = orig.mPivotYRel;
mPivotY = orig.mPivotY;
- mFromDegrees = mCurrentDegrees = orig.mFromDegrees;
+ mFromDegrees = orig.mFromDegrees;
mToDegrees = orig.mToDegrees;
- mCanConstantState = mCheckedConstantState = true;
+ mCurrentDegrees = orig.mCurrentDegrees;
+ mCheckedConstantState = mCanConstantState = true;
}
}
@Override
+ public boolean canApplyTheme() {
+ return mThemeAttrs != null || (mDrawable != null && mDrawable.canApplyTheme())
+ || super.canApplyTheme();
+ }
+
+ @Override
public Drawable newDrawable() {
return new RotateDrawable(this, null);
}
diff --git a/graphics/java/android/graphics/drawable/ScaleDrawable.java b/graphics/java/android/graphics/drawable/ScaleDrawable.java
index b990249..da722f3 100644
--- a/graphics/java/android/graphics/drawable/ScaleDrawable.java
+++ b/graphics/java/android/graphics/drawable/ScaleDrawable.java
@@ -16,6 +16,8 @@
package android.graphics.drawable;
+import com.android.internal.R;
+
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -29,6 +31,7 @@ import android.view.Gravity;
import android.util.AttributeSet;
import java.io.IOException;
+import java.util.Collection;
/**
* A Drawable that changes the size of another Drawable based on its current
@@ -47,7 +50,7 @@ import java.io.IOException;
* @attr ref android.R.styleable#ScaleDrawable_drawable
*/
public class ScaleDrawable extends Drawable implements Drawable.Callback {
- private ScaleState mScaleState;
+ private ScaleState mState;
private boolean mMutated;
private final Rect mTmpRect = new Rect();
@@ -58,10 +61,10 @@ public class ScaleDrawable extends Drawable implements Drawable.Callback {
public ScaleDrawable(Drawable drawable, int gravity, float scaleWidth, float scaleHeight) {
this(null, null);
- mScaleState.mDrawable = drawable;
- mScaleState.mGravity = gravity;
- mScaleState.mScaleWidth = scaleWidth;
- mScaleState.mScaleHeight = scaleHeight;
+ mState.mDrawable = drawable;
+ mState.mGravity = gravity;
+ mState.mScaleWidth = scaleWidth;
+ mState.mScaleHeight = scaleHeight;
if (drawable != null) {
drawable.setCallback(this);
@@ -72,18 +75,18 @@ public class ScaleDrawable extends Drawable implements Drawable.Callback {
* Returns the drawable scaled by this ScaleDrawable.
*/
public Drawable getDrawable() {
- return mScaleState.mDrawable;
+ return mState.mDrawable;
}
- private static float getPercent(TypedArray a, int name) {
- String s = a.getString(name);
+ private static float getPercent(TypedArray a, int name, float defaultValue) {
+ final String s = a.getString(name);
if (s != null) {
if (s.endsWith("%")) {
String f = s.substring(0, s.length() - 1);
return Float.parseFloat(f) / 100.0f;
}
}
- return -1;
+ return defaultValue;
}
@Override
@@ -91,20 +94,48 @@ public class ScaleDrawable extends Drawable implements Drawable.Callback {
throws XmlPullParserException, IOException {
super.inflate(r, parser, attrs, theme);
- int type;
-
- TypedArray a = obtainAttributes(
- r, theme, attrs, com.android.internal.R.styleable.ScaleDrawable);
+ final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.ScaleDrawable);
- float sw = getPercent(a, com.android.internal.R.styleable.ScaleDrawable_scaleWidth);
- float sh = getPercent(a, com.android.internal.R.styleable.ScaleDrawable_scaleHeight);
- int g = a.getInt(com.android.internal.R.styleable.ScaleDrawable_scaleGravity, Gravity.LEFT);
- boolean min = a.getBoolean(
- com.android.internal.R.styleable.ScaleDrawable_useIntrinsicSizeAsMinimum, false);
- Drawable dr = a.getDrawable(com.android.internal.R.styleable.ScaleDrawable_drawable);
+ // Reset mDrawable to preserve old multiple-inflate behavior. This is
+ // silly, but we have CTS tests that rely on it.
+ mState.mDrawable = null;
+ updateStateFromTypedArray(a);
+ inflateChildElements(r, parser, attrs, theme);
+ verifyRequiredAttributes(a);
a.recycle();
+ }
+
+ @Override
+ public void applyTheme(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();
+ }
+ }
+ if (state.mDrawable != null && state.mDrawable.canApplyTheme()) {
+ state.mDrawable.applyTheme(t);
+ }
+ }
+
+ private void inflateChildElements(Resources r, XmlPullParser parser, AttributeSet attrs,
+ Theme theme) throws XmlPullParserException, IOException {
+ Drawable dr = null;
+ int type;
final int outerDepth = parser.getDepth();
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
@@ -114,20 +145,51 @@ public class ScaleDrawable extends Drawable implements Drawable.Callback {
dr = Drawable.createFromXmlInner(r, parser, attrs, theme);
}
- if (dr == null) {
- throw new IllegalArgumentException("No drawable specified for <scale>");
+ if (dr != null) {
+ mState.mDrawable = dr;
+ dr.setCallback(this);
}
+ }
+
+ private void verifyRequiredAttributes(TypedArray a) throws XmlPullParserException {
+ // If we're not waiting on a theme, verify required attributes.
+ if (mState.mDrawable == null && (mState.mThemeAttrs == null
+ || mState.mThemeAttrs[R.styleable.ScaleDrawable_drawable] == 0)) {
+ throw new XmlPullParserException(a.getPositionDescription()
+ + ": <scale> tag requires a 'drawable' attribute or "
+ + "child tag defining a drawable");
+ }
+ }
+
+ private void updateStateFromTypedArray(TypedArray a) {
+ final ScaleState state = mState;
- mScaleState.mDrawable = dr;
- mScaleState.mScaleWidth = sw;
- mScaleState.mScaleHeight = sh;
- mScaleState.mGravity = g;
- mScaleState.mUseIntrinsicSizeAsMin = min;
+ // 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, R.styleable.ScaleDrawable_scaleHeight, state.mScaleHeight);
+ state.mGravity = a.getInt(R.styleable.ScaleDrawable_scaleGravity, state.mGravity);
+ state.mUseIntrinsicSizeAsMin = a.getBoolean(
+ R.styleable.ScaleDrawable_useIntrinsicSizeAsMinimum, state.mUseIntrinsicSizeAsMin);
+
+ final Drawable dr = a.getDrawable(R.styleable.ScaleDrawable_drawable);
if (dr != null) {
+ state.mDrawable = dr;
dr.setCallback(this);
}
}
+ @Override
+ public boolean canApplyTheme() {
+ return (mState != null && mState.canApplyTheme()) || super.canApplyTheme();
+ }
+
// overrides from Drawable.Callback
public void invalidateDrawable(Drawable who) {
@@ -152,74 +214,74 @@ public class ScaleDrawable extends Drawable implements Drawable.Callback {
@Override
public void draw(Canvas canvas) {
- if (mScaleState.mDrawable.getLevel() != 0)
- mScaleState.mDrawable.draw(canvas);
+ if (mState.mDrawable.getLevel() != 0)
+ mState.mDrawable.draw(canvas);
}
@Override
public int getChangingConfigurations() {
return super.getChangingConfigurations()
- | mScaleState.mChangingConfigurations
- | mScaleState.mDrawable.getChangingConfigurations();
+ | mState.mChangingConfigurations
+ | mState.mDrawable.getChangingConfigurations();
}
@Override
public boolean getPadding(Rect padding) {
// XXX need to adjust padding!
- return mScaleState.mDrawable.getPadding(padding);
+ return mState.mDrawable.getPadding(padding);
}
@Override
public boolean setVisible(boolean visible, boolean restart) {
- mScaleState.mDrawable.setVisible(visible, restart);
+ mState.mDrawable.setVisible(visible, restart);
return super.setVisible(visible, restart);
}
@Override
public void setAlpha(int alpha) {
- mScaleState.mDrawable.setAlpha(alpha);
+ mState.mDrawable.setAlpha(alpha);
}
@Override
public int getAlpha() {
- return mScaleState.mDrawable.getAlpha();
+ return mState.mDrawable.getAlpha();
}
@Override
public void setColorFilter(ColorFilter cf) {
- mScaleState.mDrawable.setColorFilter(cf);
+ mState.mDrawable.setColorFilter(cf);
}
@Override
public void setTintList(ColorStateList tint) {
- mScaleState.mDrawable.setTintList(tint);
+ mState.mDrawable.setTintList(tint);
}
@Override
public void setTintMode(Mode tintMode) {
- mScaleState.mDrawable.setTintMode(tintMode);
+ mState.mDrawable.setTintMode(tintMode);
}
@Override
public int getOpacity() {
- return mScaleState.mDrawable.getOpacity();
+ return mState.mDrawable.getOpacity();
}
@Override
public boolean isStateful() {
- return mScaleState.mDrawable.isStateful();
+ return mState.mDrawable.isStateful();
}
@Override
protected boolean onStateChange(int[] state) {
- boolean changed = mScaleState.mDrawable.setState(state);
+ boolean changed = mState.mDrawable.setState(state);
onBoundsChange(getBounds());
return changed;
}
@Override
protected boolean onLevelChange(int level) {
- mScaleState.mDrawable.setLevel(level);
+ mState.mDrawable.setLevel(level);
onBoundsChange(getBounds());
invalidateSelf();
return true;
@@ -228,41 +290,41 @@ public class ScaleDrawable extends Drawable implements Drawable.Callback {
@Override
protected void onBoundsChange(Rect bounds) {
final Rect r = mTmpRect;
- final boolean min = mScaleState.mUseIntrinsicSizeAsMin;
+ final boolean min = mState.mUseIntrinsicSizeAsMin;
int level = getLevel();
int w = bounds.width();
- if (mScaleState.mScaleWidth > 0) {
- final int iw = min ? mScaleState.mDrawable.getIntrinsicWidth() : 0;
- w -= (int) ((w - iw) * (10000 - level) * mScaleState.mScaleWidth / 10000);
+ if (mState.mScaleWidth > 0) {
+ final int iw = min ? mState.mDrawable.getIntrinsicWidth() : 0;
+ w -= (int) ((w - iw) * (10000 - level) * mState.mScaleWidth / 10000);
}
int h = bounds.height();
- if (mScaleState.mScaleHeight > 0) {
- final int ih = min ? mScaleState.mDrawable.getIntrinsicHeight() : 0;
- h -= (int) ((h - ih) * (10000 - level) * mScaleState.mScaleHeight / 10000);
+ if (mState.mScaleHeight > 0) {
+ final int ih = min ? mState.mDrawable.getIntrinsicHeight() : 0;
+ h -= (int) ((h - ih) * (10000 - level) * mState.mScaleHeight / 10000);
}
final int layoutDirection = getLayoutDirection();
- Gravity.apply(mScaleState.mGravity, w, h, bounds, r, layoutDirection);
+ Gravity.apply(mState.mGravity, w, h, bounds, r, layoutDirection);
if (w > 0 && h > 0) {
- mScaleState.mDrawable.setBounds(r.left, r.top, r.right, r.bottom);
+ mState.mDrawable.setBounds(r.left, r.top, r.right, r.bottom);
}
}
@Override
public int getIntrinsicWidth() {
- return mScaleState.mDrawable.getIntrinsicWidth();
+ return mState.mDrawable.getIntrinsicWidth();
}
@Override
public int getIntrinsicHeight() {
- return mScaleState.mDrawable.getIntrinsicHeight();
+ return mState.mDrawable.getIntrinsicHeight();
}
@Override
public ConstantState getConstantState() {
- if (mScaleState.canConstantState()) {
- mScaleState.mChangingConfigurations = getChangingConfigurations();
- return mScaleState;
+ if (mState.canConstantState()) {
+ mState.mChangingConfigurations = getChangingConfigurations();
+ return mState;
}
return null;
}
@@ -270,25 +332,42 @@ public class ScaleDrawable extends Drawable implements Drawable.Callback {
@Override
public Drawable mutate() {
if (!mMutated && super.mutate() == this) {
- mScaleState.mDrawable.mutate();
+ mState.mDrawable.mutate();
mMutated = true;
}
return this;
}
+ /**
+ * @hide
+ */
+ public void clearMutated() {
+ super.clearMutated();
+ mState.mDrawable.clearMutated();
+ mMutated = false;
+ }
+
final static class ScaleState extends ConstantState {
- Drawable mDrawable;
+ /** Constant used to disable scaling for a particular dimension. */
+ private static final float DO_NOT_SCALE = -1.0f;
+
+ int[] mThemeAttrs;
int mChangingConfigurations;
- float mScaleWidth;
- float mScaleHeight;
- int mGravity;
- boolean mUseIntrinsicSizeAsMin;
+
+ Drawable mDrawable;
+
+ float mScaleWidth = DO_NOT_SCALE;
+ float mScaleHeight = DO_NOT_SCALE;
+ int mGravity = Gravity.LEFT;
+ boolean mUseIntrinsicSizeAsMin = false;
private boolean mCheckedConstantState;
private boolean mCanConstantState;
ScaleState(ScaleState orig, ScaleDrawable owner, Resources res) {
if (orig != null) {
+ mThemeAttrs = orig.mThemeAttrs;
+ mChangingConfigurations = orig.mChangingConfigurations;
if (res != null) {
mDrawable = orig.mDrawable.getConstantState().newDrawable(res);
} else {
@@ -307,6 +386,12 @@ public class ScaleDrawable extends Drawable implements Drawable.Callback {
}
@Override
+ public boolean canApplyTheme() {
+ return mThemeAttrs != null || (mDrawable != null && mDrawable.canApplyTheme())
+ || super.canApplyTheme();
+ }
+
+ @Override
public Drawable newDrawable() {
return new ScaleDrawable(this, null);
}
@@ -329,10 +414,19 @@ public class ScaleDrawable extends Drawable implements Drawable.Callback {
return mCanConstantState;
}
+
+ @Override
+ public int addAtlasableBitmaps(Collection<Bitmap> atlasList) {
+ final ConstantState state = mDrawable.getConstantState();
+ if (state != null) {
+ return state.addAtlasableBitmaps(atlasList);
+ }
+ return 0;
+ }
}
private ScaleDrawable(ScaleState state, Resources res) {
- mScaleState = new ScaleState(state, this, res);
+ mState = new ScaleState(state, this, res);
}
}
diff --git a/graphics/java/android/graphics/drawable/ShapeDrawable.java b/graphics/java/android/graphics/drawable/ShapeDrawable.java
index bd69d76..72edf03 100644
--- a/graphics/java/android/graphics/drawable/ShapeDrawable.java
+++ b/graphics/java/android/graphics/drawable/ShapeDrawable.java
@@ -53,9 +53,9 @@ import java.io.IOException;
* For more information about how to use ShapeDrawable, read the <a
* href="{@docRoot}guide/topics/graphics/2d-graphics.html#shape-drawable">
* Canvas and Drawables</a> document. For more information about defining a
- * ShapeDrawable in XML, read the <a href="{@docRoot}
- * guide/topics/resources/drawable-resource.html#Shape">Drawable Resources</a>
- * document.
+ * ShapeDrawable in XML, read the
+ * <a href="{@docRoot}guide/topics/resources/drawable-resource.html#Shape">
+ * Drawable Resources</a> document.
* </p>
* </div>
*
@@ -76,7 +76,7 @@ public class ShapeDrawable extends Drawable {
* ShapeDrawable constructor.
*/
public ShapeDrawable() {
- this(new ShapeState(null), null, null);
+ this(new ShapeState(null), null);
}
/**
@@ -85,7 +85,7 @@ public class ShapeDrawable extends Drawable {
* @param s the Shape that this ShapeDrawable should be
*/
public ShapeDrawable(Shape s) {
- this(new ShapeState(null), null, null);
+ this(new ShapeState(null), null);
mShapeState.mShape = s;
}
@@ -508,6 +508,14 @@ public class ShapeDrawable extends Drawable {
}
/**
+ * @hide
+ */
+ public void clearMutated() {
+ super.clearMutated();
+ mMutated = false;
+ }
+
+ /**
* Defines the intrinsic properties of this ShapeDrawable's Shape.
*/
final static class ShapeState extends ConstantState {
@@ -547,17 +555,12 @@ public class ShapeDrawable extends Drawable {
@Override
public Drawable newDrawable() {
- return new ShapeDrawable(this, null, null);
+ return new ShapeDrawable(this, null);
}
@Override
public Drawable newDrawable(Resources res) {
- return new ShapeDrawable(this, res, null);
- }
-
- @Override
- public Drawable newDrawable(Resources res, Theme theme) {
- return new ShapeDrawable(this, res, theme);
+ return new ShapeDrawable(this, res);
}
@Override
@@ -570,13 +573,8 @@ public class ShapeDrawable extends Drawable {
* The one constructor to rule them all. This is called by all public
* constructors to set the state and initialize local properties.
*/
- private ShapeDrawable(ShapeState state, Resources res, Theme theme) {
- if (theme != null && state.canApplyTheme()) {
- mShapeState = new ShapeState(state);
- applyTheme(theme);
- } else {
- mShapeState = state;
- }
+ private ShapeDrawable(ShapeState state, Resources res) {
+ mShapeState = state;
initializeWithState(state, res);
}
diff --git a/graphics/java/android/graphics/drawable/StateListDrawable.java b/graphics/java/android/graphics/drawable/StateListDrawable.java
index 2eb8a80..59d0ba6 100644
--- a/graphics/java/android/graphics/drawable/StateListDrawable.java
+++ b/graphics/java/android/graphics/drawable/StateListDrawable.java
@@ -24,6 +24,8 @@ import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.util.Arrays;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.Resources.Theme;
@@ -117,26 +119,48 @@ public class StateListDrawable extends DrawableContainer {
@Override
public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
throws XmlPullParserException, IOException {
-
final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.StateListDrawable);
+ super.inflateWithAttributes(r, parser, a, R.styleable.StateListDrawable_visible);
+ updateStateFromTypedArray(a);
+ a.recycle();
- super.inflateWithAttributes(r, parser, a,
- R.styleable.StateListDrawable_visible);
-
- mStateListState.setVariablePadding(a.getBoolean(
- R.styleable.StateListDrawable_variablePadding, false));
- mStateListState.setConstantSize(a.getBoolean(
- R.styleable.StateListDrawable_constantSize, false));
- mStateListState.setEnterFadeDuration(a.getInt(
- R.styleable.StateListDrawable_enterFadeDuration, 0));
- mStateListState.setExitFadeDuration(a.getInt(
- R.styleable.StateListDrawable_exitFadeDuration, 0));
+ inflateChildElements(r, parser, attrs, theme);
- setDither(a.getBoolean(R.styleable.StateListDrawable_dither, DEFAULT_DITHER));
- setAutoMirrored(a.getBoolean(R.styleable.StateListDrawable_autoMirrored, false));
+ onStateChange(getState());
+ }
- a.recycle();
+ /**
+ * Updates the constant state from the values in the typed array.
+ */
+ private void updateStateFromTypedArray(TypedArray a) {
+ final StateListState state = mStateListState;
+
+ // Account for any configuration changes.
+ state.mChangingConfigurations |= a.getChangingConfigurations();
+
+ // Extract the theme attributes, if any.
+ state.mThemeAttrs = a.extractThemeAttrs();
+
+ state.mVariablePadding = a.getBoolean(
+ R.styleable.StateListDrawable_variablePadding, state.mVariablePadding);
+ state.mConstantSize = a.getBoolean(
+ R.styleable.StateListDrawable_constantSize, state.mConstantSize);
+ state.mEnterFadeDuration = a.getInt(
+ R.styleable.StateListDrawable_enterFadeDuration, state.mEnterFadeDuration);
+ state.mExitFadeDuration = a.getInt(
+ R.styleable.StateListDrawable_exitFadeDuration, state.mExitFadeDuration);
+ state.mDither = a.getBoolean(
+ R.styleable.StateListDrawable_dither, state.mDither);
+ state.mAutoMirrored = a.getBoolean(
+ R.styleable.StateListDrawable_autoMirrored, state.mAutoMirrored);
+ }
+ /**
+ * Inflates child elements from XML.
+ */
+ private void inflateChildElements(Resources r, XmlPullParser parser, AttributeSet attrs,
+ Theme theme) throws XmlPullParserException, IOException {
+ final StateListState state = mStateListState;
final int innerDepth = parser.getDepth() + 1;
int type;
int depth;
@@ -151,29 +175,19 @@ public class StateListDrawable extends DrawableContainer {
continue;
}
- int drawableRes = 0;
-
- int i;
- int j = 0;
- final int numAttrs = attrs.getAttributeCount();
- int[] states = new int[numAttrs];
- for (i = 0; i < numAttrs; i++) {
- final int stateResId = attrs.getAttributeNameResource(i);
- if (stateResId == 0) break;
- if (stateResId == R.attr.drawable) {
- drawableRes = attrs.getAttributeResourceValue(i, 0);
- } else {
- states[j++] = attrs.getAttributeBooleanValue(i, false)
- ? stateResId
- : -stateResId;
- }
- }
- states = StateSet.trimStateSet(states, j);
+ // This allows state list drawable item elements to be themed at
+ // inflation time but does NOT make them work for Zygote preload.
+ final TypedArray a = obtainAttributes(r, theme, attrs,
+ R.styleable.StateListDrawableItem);
+ Drawable dr = a.getDrawable(R.styleable.StateListDrawableItem_drawable);
+ a.recycle();
- final Drawable dr;
- if (drawableRes != 0) {
- dr = r.getDrawable(drawableRes, theme);
- } else {
+ final int[] states = extractStateSet(attrs);
+
+ // Loading child elements modifies the state of the AttributeSet's
+ // underlying parser, so it needs to happen after obtaining
+ // attributes and extracting states.
+ if (dr == null) {
while ((type = parser.next()) == XmlPullParser.TEXT) {
}
if (type != XmlPullParser.START_TAG) {
@@ -185,10 +199,37 @@ public class StateListDrawable extends DrawableContainer {
dr = Drawable.createFromXmlInner(r, parser, attrs, theme);
}
- mStateListState.addStateSet(states, dr);
+ state.addStateSet(states, dr);
}
+ }
- onStateChange(getState());
+ /**
+ * Extracts state_ attributes from an attribute set.
+ *
+ * @param attrs The attribute set.
+ * @return An array of state_ attributes.
+ */
+ int[] extractStateSet(AttributeSet attrs) {
+ int j = 0;
+ final int numAttrs = attrs.getAttributeCount();
+ int[] states = new int[numAttrs];
+ for (int i = 0; i < numAttrs; i++) {
+ final int stateResId = attrs.getAttributeNameResource(i);
+ switch (stateResId) {
+ case 0:
+ break;
+ case R.attr.drawable:
+ case R.attr.id:
+ // Ignore attributes from StateListDrawableItem and
+ // AnimatedStateListDrawableItem.
+ continue;
+ default:
+ states[j++] = attrs.getAttributeBooleanValue(i, false)
+ ? stateResId : -stateResId;
+ }
+ }
+ states = StateSet.trimStateSet(states, j);
+ return states;
}
StateListState getStateListState() {
@@ -249,20 +290,25 @@ public class StateListDrawable extends DrawableContainer {
@Override
public Drawable mutate() {
if (!mMutated && super.mutate() == this) {
- final int[][] sets = mStateListState.mStateSets;
- final int count = sets.length;
- mStateListState.mStateSets = new int[count][];
- for (int i = 0; i < count; i++) {
- final int[] set = sets[i];
- if (set != null) {
- mStateListState.mStateSets[i] = set.clone();
- }
- }
+ mStateListState.mutate();
mMutated = true;
}
return this;
}
+ @Override
+ StateListState cloneConstantState() {
+ return new StateListState(mStateListState, this, null);
+ }
+
+ /**
+ * @hide
+ */
+ public void clearMutated() {
+ super.clearMutated();
+ mMutated = false;
+ }
+
/** @hide */
@Override
public void setLayoutDirection(int layoutDirection) {
@@ -274,18 +320,31 @@ public class StateListDrawable extends DrawableContainer {
}
static class StateListState extends DrawableContainerState {
+ int[] mThemeAttrs;
int[][] mStateSets;
StateListState(StateListState orig, StateListDrawable owner, Resources res) {
super(orig, owner, res);
if (orig != null) {
- mStateSets = Arrays.copyOf(orig.mStateSets, orig.mStateSets.length);
+ // Perform a shallow copy and rely on mutate() to deep-copy.
+ mThemeAttrs = orig.mThemeAttrs;
+ mStateSets = orig.mStateSets;
} else {
+ mThemeAttrs = null;
mStateSets = new int[getCapacity()][];
}
}
+ private void mutate() {
+ mThemeAttrs = mThemeAttrs != null ? mThemeAttrs.clone() : null;
+
+ final int[][] stateSets = new int[mStateSets.length][];
+ for (int i = mStateSets.length - 1; i >= 0; i--) {
+ stateSets[i] = mStateSets[i] != null ? mStateSets[i].clone() : null;
+ }
+ }
+
int addStateSet(int[] stateSet, Drawable drawable) {
final int pos = addChild(drawable);
mStateSets[pos] = stateSet;
@@ -314,6 +373,11 @@ public class StateListDrawable extends DrawableContainer {
}
@Override
+ public boolean canApplyTheme() {
+ return mThemeAttrs != null || super.canApplyTheme();
+ }
+
+ @Override
public void growArray(int oldSize, int newSize) {
super.growArray(oldSize, newSize);
final int[][] newStateSets = new int[newSize][];
@@ -322,13 +386,23 @@ public class StateListDrawable extends DrawableContainer {
}
}
- void setConstantState(StateListState state) {
+ @Override
+ public void applyTheme(Theme theme) {
+ super.applyTheme(theme);
+
+ onStateChange(getState());
+ }
+
+ protected void setConstantState(@NonNull DrawableContainerState state) {
super.setConstantState(state);
- mStateListState = state;
+ if (state instanceof StateListState) {
+ mStateListState = (StateListState) state;
+ }
}
private StateListDrawable(StateListState state, Resources res) {
+ // Every state list drawable has its own constant state.
final StateListState newState = new StateListState(state, this, res);
setConstantState(newState);
onStateChange(getState());
@@ -338,7 +412,7 @@ public class StateListDrawable extends DrawableContainer {
* This constructor exists so subclasses can avoid calling the default
* constructor and setting up a StateListDrawable-specific constant state.
*/
- StateListDrawable(StateListState state) {
+ StateListDrawable(@Nullable StateListState state) {
if (state != null) {
setConstantState(state);
}
diff --git a/graphics/java/android/graphics/drawable/TransitionDrawable.java b/graphics/java/android/graphics/drawable/TransitionDrawable.java
index 4380ca4..e5c235e 100644
--- a/graphics/java/android/graphics/drawable/TransitionDrawable.java
+++ b/graphics/java/android/graphics/drawable/TransitionDrawable.java
@@ -17,7 +17,6 @@
package android.graphics.drawable;
import android.content.res.Resources;
-import android.content.res.Resources.Theme;
import android.graphics.Canvas;
import android.os.SystemClock;
@@ -86,11 +85,11 @@ public class TransitionDrawable extends LayerDrawable implements Drawable.Callba
* @see #TransitionDrawable(Drawable[])
*/
TransitionDrawable() {
- this(new TransitionState(null, null, null), null, null);
+ this(new TransitionState(null, null, null), (Resources) null);
}
- private TransitionDrawable(TransitionState state, Resources res, Theme theme) {
- super(state, res, theme);
+ private TransitionDrawable(TransitionState state, Resources res) {
+ super(state, res);
}
private TransitionDrawable(TransitionState state, Drawable[] layers) {
@@ -245,24 +244,18 @@ public class TransitionDrawable extends LayerDrawable implements Drawable.Callba
}
static class TransitionState extends LayerState {
- TransitionState(TransitionState orig, TransitionDrawable owner,
- Resources res) {
+ TransitionState(TransitionState orig, TransitionDrawable owner, Resources res) {
super(orig, owner, res);
}
@Override
public Drawable newDrawable() {
- return new TransitionDrawable(this, null, null);
+ return new TransitionDrawable(this, (Resources) null);
}
@Override
public Drawable newDrawable(Resources res) {
- return new TransitionDrawable(this, res, null);
- }
-
- @Override
- public Drawable newDrawable(Resources res, Theme theme) {
- return new TransitionDrawable(this, res, theme);
+ return new TransitionDrawable(this, res);
}
@Override
diff --git a/graphics/java/android/graphics/drawable/VectorDrawable.java b/graphics/java/android/graphics/drawable/VectorDrawable.java
index db0c94f..8b0f635 100644
--- a/graphics/java/android/graphics/drawable/VectorDrawable.java
+++ b/graphics/java/android/graphics/drawable/VectorDrawable.java
@@ -14,6 +14,7 @@
package android.graphics.drawable;
+import android.annotation.NonNull;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
@@ -212,15 +213,8 @@ public class VectorDrawable extends Drawable {
mVectorState = new VectorDrawableState();
}
- private VectorDrawable(VectorDrawableState state, Resources res, Theme theme) {
- if (theme != null && state.canApplyTheme()) {
- // If we need to apply a theme, implicitly mutate.
- mVectorState = new VectorDrawableState(state);
- applyTheme(theme);
- } else {
- mVectorState = state;
- }
-
+ private VectorDrawable(@NonNull VectorDrawableState state) {
+ mVectorState = state;
mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
}
@@ -233,6 +227,14 @@ public class VectorDrawable extends Drawable {
return this;
}
+ /**
+ * @hide
+ */
+ public void clearMutated() {
+ super.clearMutated();
+ mMutated = false;
+ }
+
Object getTargetByName(String name) {
return mVectorState.mVPathRenderer.mVGTargetsMap.get(name);
}
@@ -359,7 +361,7 @@ public class VectorDrawable extends Drawable {
@Override
public boolean canApplyTheme() {
- return super.canApplyTheme() || mVectorState != null && mVectorState.canApplyTheme();
+ return (mVectorState != null && mVectorState.canApplyTheme()) || super.canApplyTheme();
}
@Override
@@ -748,8 +750,8 @@ public class VectorDrawable extends Drawable {
@Override
public boolean canApplyTheme() {
- return super.canApplyTheme() || mThemeAttrs != null
- || (mVPathRenderer != null && mVPathRenderer.canApplyTheme());
+ return mThemeAttrs != null || (mVPathRenderer != null && mVPathRenderer.canApplyTheme())
+ || super.canApplyTheme();
}
public VectorDrawableState() {
@@ -758,17 +760,12 @@ public class VectorDrawable extends Drawable {
@Override
public Drawable newDrawable() {
- return new VectorDrawable(this, null, null);
+ return new VectorDrawable(this);
}
@Override
public Drawable newDrawable(Resources res) {
- return new VectorDrawable(this, res, null);
- }
-
- @Override
- public Drawable newDrawable(Resources res, Theme theme) {
- return new VectorDrawable(this, res, theme);
+ return new VectorDrawable(this);
}
@Override
diff --git a/graphics/java/android/graphics/pdf/PdfEditor.java b/graphics/java/android/graphics/pdf/PdfEditor.java
index 9837139..2b70b6a 100644
--- a/graphics/java/android/graphics/pdf/PdfEditor.java
+++ b/graphics/java/android/graphics/pdf/PdfEditor.java
@@ -17,6 +17,10 @@
package android.graphics.pdf;
import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Matrix;
+import android.graphics.Point;
+import android.graphics.Rect;
import android.os.ParcelFileDescriptor;
import android.system.ErrnoException;
import android.system.OsConstants;
@@ -55,6 +59,10 @@ public final class PdfEditor {
*
* @param input Seekable file descriptor to read from.
*
+ * @throws java.io.IOException If an error occurs while reading the file.
+ * @throws java.lang.SecurityException If the file requires a password or
+ * the security scheme is not supported.
+ *
* @see #close()
*/
public PdfEditor(@NonNull ParcelFileDescriptor input) throws IOException {
@@ -98,6 +106,109 @@ public final class PdfEditor {
}
/**
+ * Sets a transformation and clip for a given page. The transformation matrix if
+ * non-null must be affine as per {@link android.graphics.Matrix#isAffine()}. If
+ * the clip is null, then no clipping is performed.
+ *
+ * @param pageIndex The page whose transform to set.
+ * @param transform The transformation to apply.
+ * @param clip The clip to apply.
+ */
+ public void setTransformAndClip(int pageIndex, @Nullable Matrix transform,
+ @Nullable Rect clip) {
+ throwIfClosed();
+ throwIfPageNotInDocument(pageIndex);
+ throwIfNotNullAndNotAfine(transform);
+ if (transform == null) {
+ transform = Matrix.IDENTITY_MATRIX;
+ }
+ if (clip == null) {
+ Point size = new Point();
+ getPageSize(pageIndex, size);
+ nativeSetTransformAndClip(mNativeDocument, pageIndex, transform.native_instance,
+ 0, 0, size.x, size.y);
+ } else {
+ nativeSetTransformAndClip(mNativeDocument, pageIndex, transform.native_instance,
+ clip.left, clip.top, clip.right, clip.bottom);
+ }
+ }
+
+ /**
+ * Gets the size of a given page in mils (1/72").
+ *
+ * @param pageIndex The page index.
+ * @param outSize The size output.
+ */
+ public void getPageSize(int pageIndex, @NonNull Point outSize) {
+ throwIfClosed();
+ throwIfOutSizeNull(outSize);
+ throwIfPageNotInDocument(pageIndex);
+ nativeGetPageSize(mNativeDocument, pageIndex, outSize);
+ }
+
+ /**
+ * Gets the media box of a given page in mils (1/72").
+ *
+ * @param pageIndex The page index.
+ * @param outMediaBox The media box output.
+ */
+ public boolean getPageMediaBox(int pageIndex, @NonNull Rect outMediaBox) {
+ throwIfClosed();
+ throwIfOutMediaBoxNull(outMediaBox);
+ throwIfPageNotInDocument(pageIndex);
+ return nativeGetPageMediaBox(mNativeDocument, pageIndex, outMediaBox);
+ }
+
+ /**
+ * Sets the media box of a given page in mils (1/72").
+ *
+ * @param pageIndex The page index.
+ * @param mediaBox The media box.
+ */
+ public void setPageMediaBox(int pageIndex, @NonNull Rect mediaBox) {
+ throwIfClosed();
+ throwIfMediaBoxNull(mediaBox);
+ throwIfPageNotInDocument(pageIndex);
+ nativeSetPageMediaBox(mNativeDocument, pageIndex, mediaBox);
+ }
+
+ /**
+ * Gets the crop box of a given page in mils (1/72").
+ *
+ * @param pageIndex The page index.
+ * @param outCropBox The crop box output.
+ */
+ public boolean getPageCropBox(int pageIndex, @NonNull Rect outCropBox) {
+ throwIfClosed();
+ throwIfOutCropBoxNull(outCropBox);
+ throwIfPageNotInDocument(pageIndex);
+ return nativeGetPageCropBox(mNativeDocument, pageIndex, outCropBox);
+ }
+
+ /**
+ * Sets the crop box of a given page in mils (1/72").
+ *
+ * @param pageIndex The page index.
+ * @param cropBox The crop box.
+ */
+ public void setPageCropBox(int pageIndex, @NonNull Rect cropBox) {
+ throwIfClosed();
+ throwIfCropBoxNull(cropBox);
+ throwIfPageNotInDocument(pageIndex);
+ nativeSetPageCropBox(mNativeDocument, pageIndex, cropBox);
+ }
+
+ /**
+ * Gets whether the document prefers to be scaled for printing.
+ *
+ * @return Whether to scale the document.
+ */
+ public boolean shouldScaleForPrinting() {
+ throwIfClosed();
+ return nativeScaleForPrinting(mNativeDocument);
+ }
+
+ /**
* Writes the PDF file to the provided destination.
* <p>
* <strong>Note:</strong> This method takes ownership of the passed in file
@@ -154,9 +265,57 @@ public final class PdfEditor {
}
}
+ private void throwIfNotNullAndNotAfine(Matrix matrix) {
+ if (matrix != null && !matrix.isAffine()) {
+ throw new IllegalStateException("Matrix must be afine");
+ }
+ }
+
+ private void throwIfOutSizeNull(Point outSize) {
+ if (outSize == null) {
+ throw new NullPointerException("outSize cannot be null");
+ }
+ }
+
+ private void throwIfOutMediaBoxNull(Rect outMediaBox) {
+ if (outMediaBox == null) {
+ throw new NullPointerException("outMediaBox cannot be null");
+ }
+ }
+
+ private void throwIfMediaBoxNull(Rect mediaBox) {
+ if (mediaBox == null) {
+ throw new NullPointerException("mediaBox cannot be null");
+ }
+ }
+
+ private void throwIfOutCropBoxNull(Rect outCropBox) {
+ if (outCropBox == null) {
+ throw new NullPointerException("outCropBox cannot be null");
+ }
+ }
+
+ private void throwIfCropBoxNull(Rect cropBox) {
+ if (cropBox == null) {
+ throw new NullPointerException("cropBox cannot be null");
+ }
+ }
+
private static native long nativeOpen(int fd, long size);
private static native void nativeClose(long documentPtr);
private static native int nativeGetPageCount(long documentPtr);
private static native int nativeRemovePage(long documentPtr, int pageIndex);
private static native void nativeWrite(long documentPtr, int fd);
+ private static native void nativeSetTransformAndClip(long documentPtr, int pageIndex,
+ long transformPtr, int clipLeft, int clipTop, int clipRight, int clipBottom);
+ private static native void nativeGetPageSize(long documentPtr, int pageIndex, Point outSize);
+ private static native boolean nativeGetPageMediaBox(long documentPtr, int pageIndex,
+ Rect outMediaBox);
+ private static native void nativeSetPageMediaBox(long documentPtr, int pageIndex,
+ Rect mediaBox);
+ private static native boolean nativeGetPageCropBox(long documentPtr, int pageIndex,
+ Rect outMediaBox);
+ private static native void nativeSetPageCropBox(long documentPtr, int pageIndex,
+ Rect mediaBox);
+ private static native boolean nativeScaleForPrinting(long documentPtr);
}
diff --git a/graphics/java/android/graphics/pdf/PdfRenderer.java b/graphics/java/android/graphics/pdf/PdfRenderer.java
index 359c294..79934da 100644
--- a/graphics/java/android/graphics/pdf/PdfRenderer.java
+++ b/graphics/java/android/graphics/pdf/PdfRenderer.java
@@ -131,6 +131,10 @@ public final class PdfRenderer implements AutoCloseable {
* </p>
*
* @param input Seekable file descriptor to read from.
+ *
+ * @throws java.io.IOException If an error occurs while reading the file.
+ * @throws java.lang.SecurityException If the file requires a password or
+ * the security scheme is not supported.
*/
public PdfRenderer(@NonNull ParcelFileDescriptor input) throws IOException {
if (input == null) {