diff options
Diffstat (limited to 'graphics/java/android')
34 files changed, 2113 insertions, 1485 deletions
diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java index 06cf253..ef0a411 100644 --- a/graphics/java/android/graphics/Bitmap.java +++ b/graphics/java/android/graphics/Bitmap.java @@ -194,6 +194,11 @@ public final class Bitmap implements Parcelable { * while {@link #getAllocationByteCount()} will reflect that of the initial * configuration.</p> * + * <p>Note: This may change this result of hasAlpha(). When converting to 565, + * the new bitmap will always be considered opaque. When converting from 565, + * the new bitmap will be considered non-opaque, and will respect the value + * set by setPremultiplied().</p> + * * <p>WARNING: This method should NOT be called on a bitmap currently used * by the view system. It does not make guarantees about how the underlying * pixel buffer is remapped to the new config, just that the allocation is @@ -217,7 +222,8 @@ public final class Bitmap implements Parcelable { throw new IllegalStateException("native-backed bitmaps may not be reconfigured"); } - nativeReconfigure(mNativeBitmap, width, height, config.nativeInt, mBuffer.length); + nativeReconfigure(mNativeBitmap, width, height, config.nativeInt, mBuffer.length, + mIsPremultiplied); mWidth = width; mHeight = height; } @@ -1586,7 +1592,8 @@ public final class Bitmap implements Parcelable { private static native void nativeDestructor(long nativeBitmap); private static native boolean nativeRecycle(long nativeBitmap); private static native void nativeReconfigure(long nativeBitmap, int width, int height, - int config, int allocSize); + int config, int allocSize, + boolean isPremultiplied); private static native boolean nativeCompress(long nativeBitmap, int format, int quality, OutputStream stream, diff --git a/graphics/java/android/graphics/BitmapFactory.java b/graphics/java/android/graphics/BitmapFactory.java index c20502f..bc20ea5 100644 --- a/graphics/java/android/graphics/BitmapFactory.java +++ b/graphics/java/android/graphics/BitmapFactory.java @@ -260,7 +260,11 @@ public class BitmapFactory { public boolean inScaled; /** - * If this is set to true, then the resulting bitmap will allocate its + * @deprecated As of {@link android.os.Build.VERSION_CODES#L}, this is + * ignored. + * + * In {@link android.os.Build.VERSION_CODES#KITKAT} and below, if this + * is set to true, then the resulting bitmap will allocate its * pixels such that they can be purged if the system needs to reclaim * memory. In that instance, when the pixels need to be accessed again * (e.g. the bitmap is drawn, getPixels() is called), they will be @@ -287,14 +291,20 @@ public class BitmapFactory { * android.graphics.BitmapFactory.Options)} or {@link #decodeFile(String, * android.graphics.BitmapFactory.Options)}.</p> */ + @Deprecated public boolean inPurgeable; /** - * This field works in conjuction with inPurgeable. If inPurgeable is - * false, then this field is ignored. If inPurgeable is true, then this - * field determines whether the bitmap can share a reference to the - * input data (inputstream, array, etc.) or if it must make a deep copy. + * @deprecated As of {@link android.os.Build.VERSION_CODES#L}, this is + * ignored. + * + * In {@link android.os.Build.VERSION_CODES#KITKAT} and below, this + * field works in conjuction with inPurgeable. If inPurgeable is false, + * then this field is ignored. If inPurgeable is true, then this field + * determines whether the bitmap can share a reference to the input + * data (inputstream, array, etc.) or if it must make a deep copy. */ + @Deprecated public boolean inInputShareable; /** diff --git a/graphics/java/android/graphics/Camera.java b/graphics/java/android/graphics/Camera.java index a40085b..57e0f27 100644 --- a/graphics/java/android/graphics/Camera.java +++ b/graphics/java/android/graphics/Camera.java @@ -154,7 +154,7 @@ public class Camera { getMatrix(mMatrix); canvas.concat(mMatrix); } else { - nativeApplyToCanvas(canvas.getNativeCanvas()); + nativeApplyToCanvas(canvas.getNativeCanvasWrapper()); } } diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java index d4ea7a9..99596ef 100644 --- a/graphics/java/android/graphics/Canvas.java +++ b/graphics/java/android/graphics/Canvas.java @@ -16,11 +16,17 @@ package android.graphics; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.text.GraphicsOperations; import android.text.SpannableString; import android.text.SpannedString; import android.text.TextUtils; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + import javax.microedition.khronos.opengles.GL; /** @@ -39,11 +45,11 @@ import javax.microedition.khronos.opengles.GL; public class Canvas { // assigned in constructors or setBitmap, freed in finalizer - private long mNativeCanvas; + private long mNativeCanvasWrapper; /** @hide */ - public long getNativeCanvas() { - return mNativeCanvas; + public long getNativeCanvasWrapper() { + return mNativeCanvasWrapper; } // may be null @@ -59,11 +65,11 @@ public class Canvas { /** * Used to determine when compatibility scaling is in effect. - * + * * @hide */ protected int mScreenDensity = Bitmap.DENSITY_NONE; - + // Used by native code @SuppressWarnings("UnusedDeclaration") private int mSurfaceFormat; @@ -73,7 +79,7 @@ public class Canvas { * @hide */ public static final int DIRECTION_LTR = 0; - + /** * Flag for drawTextRun indicating right-to-left run direction. * @hide @@ -88,10 +94,10 @@ public class Canvas { private final CanvasFinalizer mFinalizer; private static final class CanvasFinalizer { - private long mNativeCanvas; + private long mNativeCanvasWrapper; public CanvasFinalizer(long nativeCanvas) { - mNativeCanvas = nativeCanvas; + mNativeCanvasWrapper = nativeCanvas; } @Override @@ -104,9 +110,9 @@ public class Canvas { } public void dispose() { - if (mNativeCanvas != 0) { - finalizer(mNativeCanvas); - mNativeCanvas = 0; + if (mNativeCanvasWrapper != 0) { + finalizer(mNativeCanvasWrapper); + mNativeCanvasWrapper = 0; } } } @@ -120,8 +126,8 @@ public class Canvas { public Canvas() { if (!isHardwareAccelerated()) { // 0 means no native bitmap - mNativeCanvas = initRaster(0); - mFinalizer = new CanvasFinalizer(mNativeCanvas); + mNativeCanvasWrapper = initRaster(0); + mFinalizer = new CanvasFinalizer(mNativeCanvasWrapper); } else { mFinalizer = null; } @@ -130,19 +136,19 @@ public class Canvas { /** * Construct a canvas with the specified bitmap to draw into. The bitmap * must be mutable. - * + * * <p>The initial target density of the canvas is the same as the given * bitmap's density. * * @param bitmap Specifies a mutable bitmap for the canvas to draw into. */ - public Canvas(Bitmap bitmap) { + public Canvas(@NonNull Bitmap bitmap) { if (!bitmap.isMutable()) { throw new IllegalStateException("Immutable bitmap passed to Canvas constructor"); } throwIfCannotDraw(bitmap); - mNativeCanvas = initRaster(bitmap.ni()); - mFinalizer = new CanvasFinalizer(mNativeCanvas); + mNativeCanvasWrapper = initRaster(bitmap.ni()); + mFinalizer = new CanvasFinalizer(mNativeCanvasWrapper); mBitmap = bitmap; mDensity = bitmap.mDensity; } @@ -152,26 +158,12 @@ public class Canvas { if (nativeCanvas == 0) { throw new IllegalStateException(); } - mNativeCanvas = nativeCanvas; - mFinalizer = new CanvasFinalizer(mNativeCanvas); + mNativeCanvasWrapper = initCanvas(nativeCanvas); + mFinalizer = new CanvasFinalizer(mNativeCanvasWrapper); mDensity = Bitmap.getDefaultDensity(); } /** - * Replace existing canvas while ensuring that the swap has occurred before - * the previous native canvas is unreferenced. - */ - private void safeCanvasSwap(long nativeCanvas, boolean copyState) { - final long oldCanvas = mNativeCanvas; - mNativeCanvas = nativeCanvas; - mFinalizer.mNativeCanvas = nativeCanvas; - if (copyState) { - copyNativeCanvasState(oldCanvas, mNativeCanvas); - } - finalizer(oldCanvas); - } - - /** * Returns null. * * @deprecated This method is not supported and should not be invoked. @@ -185,10 +177,10 @@ public class Canvas { /** * Indicates whether this Canvas uses hardware acceleration. - * + * * Note that this method does not define what type of hardware acceleration * may or may not be used. - * + * * @return True if drawing operations are hardware accelerated, * false otherwise. */ @@ -197,7 +189,7 @@ public class Canvas { } /** - * Specify a bitmap for the canvas to draw into. All canvas state such as + * Specify a bitmap for the canvas to draw into. All canvas state such as * layers, filters, and the save/restore stack are reset with the exception * of the current matrix and clip stack. Additionally, as a side-effect * the canvas' target density is updated to match that of the bitmap. @@ -206,13 +198,13 @@ public class Canvas { * @see #setDensity(int) * @see #getDensity() */ - public void setBitmap(Bitmap bitmap) { + public void setBitmap(@Nullable Bitmap bitmap) { if (isHardwareAccelerated()) { throw new RuntimeException("Can't set a bitmap device on a GL canvas"); } if (bitmap == null) { - safeCanvasSwap(initRaster(0), false); + native_setBitmap(mNativeCanvasWrapper, 0, false); mDensity = Bitmap.DENSITY_NONE; } else { if (!bitmap.isMutable()) { @@ -220,7 +212,7 @@ public class Canvas { } throwIfCannotDraw(bitmap); - safeCanvasSwap(initRaster(bitmap.ni()), true); + native_setBitmap(mNativeCanvasWrapper, bitmap.ni(), true); mDensity = bitmap.mDensity; } @@ -228,6 +220,13 @@ public class Canvas { } /** + * setBitmap() variant for native callers with a raw bitmap handle. + */ + private void setNativeBitmap(long bitmapHandle) { + native_setBitmap(mNativeCanvasWrapper, bitmapHandle, false); + } + + /** * Set the viewport dimensions if this canvas is GL based. If it is not, * this method is ignored and no exception is thrown. * @@ -249,21 +248,27 @@ public class Canvas { * * @return true if the device that the current layer draws into is opaque */ - public native boolean isOpaque(); + public boolean isOpaque() { + return native_isOpaque(mNativeCanvasWrapper); + } /** * Returns the width of the current drawing layer * * @return the width of the current drawing layer */ - public native int getWidth(); + public int getWidth() { + return native_getWidth(mNativeCanvasWrapper); + } /** * Returns the height of the current drawing layer * * @return the height of the current drawing layer */ - public native int getHeight(); + public int getHeight() { + return native_getHeight(mNativeCanvasWrapper); + } /** * <p>Returns the target density of the canvas. The default density is @@ -274,7 +279,7 @@ public class Canvas { * to determine the scaling factor when drawing a bitmap into it. * * @see #setDensity(int) - * @see Bitmap#getDensity() + * @see Bitmap#getDensity() */ public int getDensity() { return mDensity; @@ -290,7 +295,7 @@ public class Canvas { * {@link Bitmap#DENSITY_NONE} to disable bitmap scaling. * * @see #getDensity() - * @see Bitmap#setDensity(int) + * @see Bitmap#setDensity(int) */ public void setDensity(int density) { if (mBitmap != null) { @@ -308,19 +313,19 @@ public class Canvas { * Returns the maximum allowed width for bitmaps drawn with this canvas. * Attempting to draw with a bitmap wider than this value will result * in an error. - * - * @see #getMaximumBitmapHeight() + * + * @see #getMaximumBitmapHeight() */ public int getMaximumBitmapWidth() { return MAXMIMUM_BITMAP_SIZE; } - + /** * Returns the maximum allowed height for bitmaps drawn with this canvas. * Attempting to draw with a bitmap taller than this value will result * in an error. - * - * @see #getMaximumBitmapWidth() + * + * @see #getMaximumBitmapWidth() */ public int getMaximumBitmapHeight() { return MAXMIMUM_BITMAP_SIZE; @@ -328,6 +333,19 @@ public class Canvas { // the SAVE_FLAG constants must match their native equivalents + /** @hide */ + @IntDef(flag = true, + value = { + MATRIX_SAVE_FLAG, + CLIP_SAVE_FLAG, + HAS_ALPHA_LAYER_SAVE_FLAG, + FULL_COLOR_LAYER_SAVE_FLAG, + CLIP_TO_LAYER_SAVE_FLAG, + ALL_SAVE_FLAG + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Saveflags {} + /** restore the current matrix when restore() is called */ public static final int MATRIX_SAVE_FLAG = 0x01; /** restore the current clip when restore() is called */ @@ -339,8 +357,8 @@ public class Canvas { /** clip against the layer's bounds */ public static final int CLIP_TO_LAYER_SAVE_FLAG = 0x10; /** restore everything when restore() is called */ - public static final int ALL_SAVE_FLAG = 0x1F; - + 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 @@ -350,8 +368,10 @@ public class Canvas { * * @return The value to pass to restoreToCount() to balance this save() */ - public native int save(); - + public int save() { + return native_save(mNativeCanvasWrapper, MATRIX_SAVE_FLAG | CLIP_SAVE_FLAG); + } + /** * Based on saveFlags, can save the current matrix and clip onto a private * stack. Subsequent calls to translate,scale,rotate,skew,concat or @@ -363,7 +383,9 @@ public class Canvas { * to save/restore * @return The value to pass to restoreToCount() to balance this save() */ - public native int save(int saveFlags); + public int save(@Saveflags int saveFlags) { + return native_save(mNativeCanvasWrapper, saveFlags); + } /** * This behaves the same as save(), but in addition it allocates an @@ -381,8 +403,9 @@ public class Canvas { * @param saveFlags see _SAVE_FLAG constants * @return value to pass to restoreToCount() to balance this save() */ - public int saveLayer(RectF bounds, Paint paint, int saveFlags) { - return native_saveLayer(mNativeCanvas, bounds, + public int saveLayer(@Nullable RectF bounds, @Nullable Paint paint, @Saveflags int saveFlags) { + return native_saveLayer(mNativeCanvasWrapper, + bounds.left, bounds.top, bounds.right, bounds.bottom, paint != null ? paint.mNativePaint : 0, saveFlags); } @@ -390,16 +413,16 @@ public class Canvas { /** * Convenience for saveLayer(bounds, paint, {@link #ALL_SAVE_FLAG}) */ - public int saveLayer(RectF bounds, Paint paint) { + public int saveLayer(@Nullable RectF bounds, @Nullable Paint paint) { return saveLayer(bounds, paint, ALL_SAVE_FLAG); } /** * Helper version of saveLayer() that takes 4 values rather than a RectF. */ - public int saveLayer(float left, float top, float right, float bottom, Paint paint, - int saveFlags) { - return native_saveLayer(mNativeCanvas, left, top, right, bottom, + public int saveLayer(float left, float top, float right, float bottom, @Nullable Paint paint, + @Saveflags int saveFlags) { + return native_saveLayer(mNativeCanvasWrapper, left, top, right, bottom, paint != null ? paint.mNativePaint : 0, saveFlags); } @@ -407,7 +430,7 @@ public class Canvas { /** * Convenience for saveLayer(left, top, right, bottom, paint, {@link #ALL_SAVE_FLAG}) */ - public int saveLayer(float left, float top, float right, float bottom, Paint paint) { + public int saveLayer(float left, float top, float right, float bottom, @Nullable Paint paint) { return saveLayer(left, top, right, bottom, paint, ALL_SAVE_FLAG); } @@ -427,15 +450,17 @@ public class Canvas { * @param saveFlags see _SAVE_FLAG constants * @return value to pass to restoreToCount() to balance this call */ - public int saveLayerAlpha(RectF bounds, int alpha, int saveFlags) { + public int saveLayerAlpha(@NonNull RectF bounds, int alpha, @Saveflags int saveFlags) { alpha = Math.min(255, Math.max(0, alpha)); - return native_saveLayerAlpha(mNativeCanvas, bounds, alpha, saveFlags); + return native_saveLayerAlpha(mNativeCanvasWrapper, + bounds.left, bounds.top, bounds.right, bounds.bottom, + alpha, saveFlags); } /** * Convenience for saveLayerAlpha(bounds, alpha, {@link #ALL_SAVE_FLAG}) */ - public int saveLayerAlpha(RectF bounds, int alpha) { + public int saveLayerAlpha(@NonNull RectF bounds, int alpha) { return saveLayerAlpha(bounds, alpha, ALL_SAVE_FLAG); } @@ -443,8 +468,8 @@ public class Canvas { * Helper for saveLayerAlpha() that takes 4 values instead of a RectF. */ public int saveLayerAlpha(float left, float top, float right, float bottom, int alpha, - int saveFlags) { - return native_saveLayerAlpha(mNativeCanvas, left, top, right, bottom, + @Saveflags int saveFlags) { + return native_saveLayerAlpha(mNativeCanvasWrapper, left, top, right, bottom, alpha, saveFlags); } @@ -460,13 +485,17 @@ public class Canvas { * modifications to the matrix/clip state since the last save call. It is * an error to call restore() more times than save() was called. */ - public native void restore(); + public void restore() { + native_restore(mNativeCanvasWrapper); + } /** * Returns the number of matrix/clip states on the Canvas' private stack. * This will equal # save() calls - # restore() calls. */ - public native int getSaveCount(); + public int getSaveCount() { + return native_getSaveCount(mNativeCanvasWrapper); + } /** * Efficient way to pop any calls to save() that happened after the save @@ -481,7 +510,9 @@ public class Canvas { * * @param saveCount The save level to restore to. */ - public native void restoreToCount(int saveCount); + public void restoreToCount(int saveCount) { + native_restoreToCount(mNativeCanvasWrapper, saveCount); + } /** * Preconcat the current matrix with the specified translation @@ -489,7 +520,9 @@ public class Canvas { * @param dx The distance to translate in X * @param dy The distance to translate in Y */ - public native void translate(float dx, float dy); + public void translate(float dx, float dy) { + native_translate(mNativeCanvasWrapper, dx, dy); + } /** * Preconcat the current matrix with the specified scale. @@ -497,7 +530,9 @@ public class Canvas { * @param sx The amount to scale in X * @param sy The amount to scale in Y */ - public native void scale(float sx, float sy); + public void scale(float sx, float sy) { + native_scale(mNativeCanvasWrapper, sx, sy); + } /** * Preconcat the current matrix with the specified scale. @@ -518,7 +553,9 @@ public class Canvas { * * @param degrees The amount to rotate, in degrees */ - public native void rotate(float degrees); + public void rotate(float degrees) { + native_rotate(mNativeCanvasWrapper, degrees); + } /** * Preconcat the current matrix with the specified rotation. @@ -539,7 +576,9 @@ public class Canvas { * @param sx The amount to skew in X * @param sy The amount to skew in Y */ - public native void skew(float sx, float sy); + public void skew(float sx, float sy) { + native_skew(mNativeCanvasWrapper, sx, sy); + } /** * Preconcat the current matrix with the specified matrix. If the specified @@ -547,35 +586,35 @@ public class Canvas { * * @param matrix The matrix to preconcatenate with the current matrix */ - public void concat(Matrix matrix) { - if (matrix != null) native_concat(mNativeCanvas, matrix.native_instance); + public void concat(@Nullable Matrix matrix) { + if (matrix != null) native_concat(mNativeCanvasWrapper, matrix.native_instance); } - + /** * Completely replace the current matrix with the specified matrix. If the * matrix parameter is null, then the current matrix is reset to identity. - * + * * <strong>Note:</strong> it is recommended to use {@link #concat(Matrix)}, * {@link #scale(float, float)}, {@link #translate(float, float)} and * {@link #rotate(float)} instead of this method. * * @param matrix The matrix to replace the current matrix with. If it is * null, set the current matrix to identity. - * - * @see #concat(Matrix) + * + * @see #concat(Matrix) */ - public void setMatrix(Matrix matrix) { - native_setMatrix(mNativeCanvas, + public void setMatrix(@Nullable Matrix matrix) { + native_setMatrix(mNativeCanvasWrapper, matrix == null ? 0 : matrix.native_instance); } - + /** * Return, in ctm, the current transformation matrix. This does not alter * the matrix in the canvas, but just returns a copy of it. */ @Deprecated - public void getMatrix(Matrix ctm) { - native_getCTM(mNativeCanvas, ctm.native_instance); + public void getMatrix(@NonNull Matrix ctm) { + native_getCTM(mNativeCanvasWrapper, ctm.native_instance); } /** @@ -583,13 +622,13 @@ public class Canvas { * matrix. */ @Deprecated - public final Matrix getMatrix() { + public final @NonNull Matrix getMatrix() { Matrix m = new Matrix(); //noinspection deprecation getMatrix(m); return m; } - + /** * Modify the current clip with the specified rectangle. * @@ -597,8 +636,8 @@ public class Canvas { * @param op How the clip is modified * @return true if the resulting clip is non-empty */ - public boolean clipRect(RectF rect, Region.Op op) { - return native_clipRect(mNativeCanvas, rect.left, rect.top, rect.right, rect.bottom, + public boolean clipRect(@NonNull RectF rect, @NonNull Region.Op op) { + return native_clipRect(mNativeCanvasWrapper, rect.left, rect.top, rect.right, rect.bottom, op.nativeInt); } @@ -610,8 +649,8 @@ public class Canvas { * @param op How the clip is modified * @return true if the resulting clip is non-empty */ - public boolean clipRect(Rect rect, Region.Op op) { - return native_clipRect(mNativeCanvas, rect.left, rect.top, rect.right, rect.bottom, + public boolean clipRect(@NonNull Rect rect, @NonNull Region.Op op) { + return native_clipRect(mNativeCanvasWrapper, rect.left, rect.top, rect.right, rect.bottom, op.nativeInt); } @@ -622,8 +661,11 @@ public class Canvas { * @param rect The rectangle to intersect with the current clip. * @return true if the resulting clip is non-empty */ - public native boolean clipRect(RectF rect); - + public boolean clipRect(@NonNull RectF rect) { + return native_clipRect(mNativeCanvasWrapper, rect.left, rect.top, rect.right, rect.bottom, + Region.Op.INTERSECT.nativeInt); + } + /** * Intersect the current clip with the specified rectangle, which is * expressed in local coordinates. @@ -631,8 +673,11 @@ public class Canvas { * @param rect The rectangle to intersect with the current clip. * @return true if the resulting clip is non-empty */ - public native boolean clipRect(Rect rect); - + public boolean clipRect(@NonNull Rect rect) { + return native_clipRect(mNativeCanvasWrapper, rect.left, rect.top, rect.right, rect.bottom, + Region.Op.INTERSECT.nativeInt); + } + /** * Modify the current clip with the specified rectangle, which is * expressed in local coordinates. @@ -648,8 +693,9 @@ public class Canvas { * @param op How the clip is modified * @return true if the resulting clip is non-empty */ - public boolean clipRect(float left, float top, float right, float bottom, Region.Op op) { - return native_clipRect(mNativeCanvas, left, top, right, bottom, op.nativeInt); + public boolean clipRect(float left, float top, float right, float bottom, + @NonNull Region.Op op) { + return native_clipRect(mNativeCanvasWrapper, left, top, right, bottom, op.nativeInt); } /** @@ -665,7 +711,10 @@ public class Canvas { * clip * @return true if the resulting clip is non-empty */ - public native boolean clipRect(float left, float top, float right, float bottom); + public boolean clipRect(float left, float top, float right, float bottom) { + return native_clipRect(mNativeCanvasWrapper, left, top, right, bottom, + Region.Op.INTERSECT.nativeInt); + } /** * Intersect the current clip with the specified rectangle, which is @@ -680,7 +729,10 @@ public class Canvas { * clip * @return true if the resulting clip is non-empty */ - public native boolean clipRect(int left, int top, int right, int bottom); + public boolean clipRect(int left, int top, int right, int bottom) { + return native_clipRect(mNativeCanvasWrapper, left, top, right, bottom, + Region.Op.INTERSECT.nativeInt); + } /** * Modify the current clip with the specified path. @@ -689,20 +741,20 @@ public class Canvas { * @param op How the clip is modified * @return true if the resulting is non-empty */ - public boolean clipPath(Path path, Region.Op op) { - return native_clipPath(mNativeCanvas, path.ni(), op.nativeInt); + public boolean clipPath(@NonNull Path path, @NonNull Region.Op op) { + return native_clipPath(mNativeCanvasWrapper, path.ni(), op.nativeInt); } - + /** * Intersect the current clip with the specified path. * * @param path The path to intersect with the current clip * @return true if the resulting is non-empty */ - public boolean clipPath(Path path) { + public boolean clipPath(@NonNull Path path) { return clipPath(path, Region.Op.INTERSECT); } - + /** * Modify the current clip with the specified region. Note that unlike * clipRect() and clipPath() which transform their arguments by the @@ -713,9 +765,12 @@ public class Canvas { * @param region The region to operate on the current clip, based on op * @param op How the clip is modified * @return true if the resulting is non-empty + * + * @deprecated Unlike all other clip calls this API does not respect the + * current matrix. Use {@link #clipRect(Rect)} as an alternative. */ - public boolean clipRegion(Region region, Region.Op op) { - return native_clipRegion(mNativeCanvas, region.ni(), op.nativeInt); + public boolean clipRegion(@NonNull Region region, @NonNull Region.Op op) { + return native_clipRegion(mNativeCanvasWrapper, region.ni(), op.nativeInt); } /** @@ -727,22 +782,25 @@ public class Canvas { * * @param region The region to operate on the current clip, based on op * @return true if the resulting is non-empty + * + * @deprecated Unlike all other clip calls this API does not respect the + * current matrix. Use {@link #clipRect(Rect)} as an alternative. */ - public boolean clipRegion(Region region) { + public boolean clipRegion(@NonNull Region region) { return clipRegion(region, Region.Op.INTERSECT); } - - public DrawFilter getDrawFilter() { + + public @Nullable DrawFilter getDrawFilter() { return mDrawFilter; } - - public void setDrawFilter(DrawFilter filter) { + + public void setDrawFilter(@Nullable DrawFilter filter) { long nativeFilter = 0; if (filter != null) { nativeFilter = filter.mNativeInt; } mDrawFilter = filter; - nativeSetDrawFilter(mNativeCanvas, nativeFilter); + nativeSetDrawFilter(mNativeCanvasWrapper, nativeFilter); } public enum EdgeType { @@ -756,7 +814,7 @@ public class Canvas { * Antialiased: Treat edges by rounding-out, since they may be antialiased */ AA(1); - + EdgeType(int nativeInt) { this.nativeInt = nativeInt; } @@ -780,8 +838,9 @@ public class Canvas { * @return true if the rect (transformed by the canvas' matrix) * does not intersect with the canvas' clip */ - public boolean quickReject(RectF rect, EdgeType type) { - return native_quickReject(mNativeCanvas, rect); + public boolean quickReject(@NonNull RectF rect, @NonNull EdgeType type) { + return native_quickReject(mNativeCanvasWrapper, + rect.left, rect.top, rect.right, rect.bottom); } /** @@ -799,8 +858,8 @@ public class Canvas { * @return true if the path (transformed by the canvas' matrix) * does not intersect with the canvas' clip */ - public boolean quickReject(Path path, EdgeType type) { - return native_quickReject(mNativeCanvas, path.ni()); + public boolean quickReject(@NonNull Path path, @NonNull EdgeType type) { + return native_quickReject(mNativeCanvasWrapper, path.ni()); } /** @@ -824,8 +883,8 @@ public class Canvas { * does not intersect with the canvas' clip */ public boolean quickReject(float left, float top, float right, float bottom, - EdgeType type) { - return native_quickReject(mNativeCanvas, left, top, right, bottom); + @NonNull EdgeType type) { + return native_quickReject(mNativeCanvasWrapper, left, top, right, bottom); } /** @@ -838,21 +897,21 @@ public class Canvas { * still return true if the current clip is non-empty. * @return true if the current clip is non-empty. */ - public boolean getClipBounds(Rect bounds) { - return native_getClipBounds(mNativeCanvas, bounds); + public boolean getClipBounds(@Nullable Rect bounds) { + return native_getClipBounds(mNativeCanvasWrapper, bounds); } - + /** * Retrieve the bounds of the current clip (in local coordinates). * * @return the clip bounds, or [0, 0, 0, 0] if the clip is empty. */ - public final Rect getClipBounds() { + public final @NonNull Rect getClipBounds() { Rect r = new Rect(); getClipBounds(r); return r; } - + /** * Fill the entire canvas' bitmap (restricted to the current clip) with the * specified RGB color, using srcover porterduff mode. @@ -862,7 +921,7 @@ public class Canvas { * @param b blue component (0..255) of the color to draw onto the canvas */ public void drawRGB(int r, int g, int b) { - native_drawRGB(mNativeCanvas, r, g, b); + native_drawRGB(mNativeCanvasWrapper, r, g, b); } /** @@ -875,7 +934,7 @@ public class Canvas { * @param b blue component (0..255) of the color to draw onto the canvas */ public void drawARGB(int a, int r, int g, int b) { - native_drawARGB(mNativeCanvas, a, r, g, b); + native_drawARGB(mNativeCanvasWrapper, a, r, g, b); } /** @@ -885,7 +944,7 @@ public class Canvas { * @param color the color to draw onto the canvas */ public void drawColor(int color) { - native_drawColor(mNativeCanvas, color); + native_drawColor(mNativeCanvasWrapper, color); } /** @@ -895,8 +954,8 @@ public class Canvas { * @param color the color to draw with * @param mode the porter-duff mode to apply to the color */ - public void drawColor(int color, PorterDuff.Mode mode) { - native_drawColor(mNativeCanvas, color, mode.nativeInt); + public void drawColor(int color, @NonNull PorterDuff.Mode mode) { + native_drawColor(mNativeCanvasWrapper, color, mode.nativeInt); } /** @@ -906,10 +965,10 @@ public class Canvas { * * @param paint The paint used to draw onto the canvas */ - public void drawPaint(Paint paint) { - native_drawPaint(mNativeCanvas, paint.mNativePaint); + public void drawPaint(@NonNull Paint paint) { + native_drawPaint(mNativeCanvasWrapper, paint.mNativePaint); } - + /** * Draw a series of points. Each point is centered at the coordinate * specified by pts[], and its diameter is specified by the paint's stroke @@ -926,19 +985,23 @@ public class Canvas { * "points" that are drawn is really (count >> 1). * @param paint The paint used to draw the points */ - public native void drawPoints(float[] pts, int offset, int count, Paint paint); + public void drawPoints(float[] pts, int offset, int count, @NonNull Paint paint) { + native_drawPoints(mNativeCanvasWrapper, pts, offset, count, paint.mNativePaint); + } /** * Helper for drawPoints() that assumes you want to draw the entire array */ - public void drawPoints(float[] pts, Paint paint) { + public void drawPoints(@NonNull float[] pts, @NonNull Paint paint) { drawPoints(pts, 0, pts.length, paint); } /** * Helper for drawPoints() for drawing a single point. */ - public native void drawPoint(float x, float y, Paint paint); + public void drawPoint(float x, float y, @NonNull Paint paint) { + native_drawPoint(mNativeCanvasWrapper, x, y, paint.mNativePaint); + } /** * Draw a line segment with the specified start and stop x,y coordinates, @@ -952,8 +1015,9 @@ public class Canvas { * @param startY The y-coordinate of the start point of the line * @param paint The paint used to draw the line */ - public void drawLine(float startX, float startY, float stopX, float stopY, Paint paint) { - native_drawLine(mNativeCanvas, startX, startY, stopX, stopY, paint.mNativePaint); + public void drawLine(float startX, float startY, float stopX, float stopY, + @NonNull Paint paint) { + native_drawLine(mNativeCanvasWrapper, startX, startY, stopX, stopY, paint.mNativePaint); } /** @@ -971,9 +1035,11 @@ public class Canvas { * (count >> 2). * @param paint The paint used to draw the points */ - public native void drawLines(float[] pts, int offset, int count, Paint paint); + public void drawLines(float[] pts, int offset, int count, Paint paint) { + native_drawLines(mNativeCanvasWrapper, pts, offset, count, paint.mNativePaint); + } - public void drawLines(float[] pts, Paint paint) { + public void drawLines(@NonNull float[] pts, @NonNull Paint paint) { drawLines(pts, 0, pts.length, paint); } @@ -984,8 +1050,9 @@ public class Canvas { * @param rect The rect to be drawn * @param paint The paint used to draw the rect */ - public void drawRect(RectF rect, Paint paint) { - native_drawRect(mNativeCanvas, rect, paint.mNativePaint); + public void drawRect(@NonNull RectF rect, @NonNull Paint paint) { + native_drawRect(mNativeCanvasWrapper, + rect.left, rect.top, rect.right, rect.bottom, paint.mNativePaint); } /** @@ -995,10 +1062,10 @@ public class Canvas { * @param r The rectangle to be drawn. * @param paint The paint used to draw the rectangle */ - public void drawRect(Rect r, Paint paint) { + public void drawRect(@NonNull Rect r, @NonNull Paint paint) { drawRect(r.left, r.top, r.right, r.bottom, paint); } - + /** * Draw the specified Rect using the specified paint. The rectangle will @@ -1010,8 +1077,8 @@ public class Canvas { * @param bottom The bottom side of the rectangle to be drawn * @param paint The paint used to draw the rect */ - public void drawRect(float left, float top, float right, float bottom, Paint paint) { - native_drawRect(mNativeCanvas, left, top, right, bottom, paint.mNativePaint); + public void drawRect(float left, float top, float right, float bottom, @NonNull Paint paint) { + native_drawRect(mNativeCanvasWrapper, left, top, right, bottom, paint.mNativePaint); } /** @@ -1020,11 +1087,19 @@ public class Canvas { * * @param oval The rectangle bounds of the oval to be drawn */ - public void drawOval(RectF oval, Paint paint) { + public void drawOval(@NonNull RectF oval, @NonNull Paint paint) { if (oval == null) { throw new NullPointerException(); } - native_drawOval(mNativeCanvas, oval, paint.mNativePaint); + drawOval(oval.left, oval.top, oval.right, oval.bottom, paint); + } + + /** + * Draw the specified oval using the specified paint. The oval will be + * filled or framed based on the Style in the paint. + */ + public void drawOval(float left, float top, float right, float bottom, @NonNull Paint paint) { + native_drawOval(mNativeCanvasWrapper, left, top, right, bottom, paint.mNativePaint); } /** @@ -1037,22 +1112,22 @@ public class Canvas { * @param radius The radius of the cirle to be drawn * @param paint The paint used to draw the circle */ - public void drawCircle(float cx, float cy, float radius, Paint paint) { - native_drawCircle(mNativeCanvas, cx, cy, radius, paint.mNativePaint); + public void drawCircle(float cx, float cy, float radius, @NonNull Paint paint) { + native_drawCircle(mNativeCanvasWrapper, cx, cy, radius, paint.mNativePaint); } /** * <p>Draw the specified arc, which will be scaled to fit inside the * specified oval.</p> - * + * * <p>If the start angle is negative or >= 360, the start angle is treated * as start angle modulo 360.</p> - * + * * <p>If the sweep angle is >= 360, then the oval is drawn * completely. Note that this differs slightly from SkPath::arcTo, which * treats the sweep angle modulo 360. If the sweep angle is negative, * the sweep angle is treated as sweep angle modulo 360</p> - * + * * <p>The arc is drawn clockwise. An angle of 0 degrees correspond to the * geometric angle of 0 degrees (3 o'clock on a watch.)</p> * @@ -1064,12 +1139,36 @@ public class Canvas { close it if it is being stroked. This will draw a wedge * @param paint The paint used to draw the arc */ - public void drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, - Paint paint) { - if (oval == null) { - throw new NullPointerException(); - } - native_drawArc(mNativeCanvas, oval, startAngle, sweepAngle, + public void drawArc(@NonNull RectF oval, float startAngle, float sweepAngle, boolean useCenter, + @NonNull Paint paint) { + drawArc(oval.left, oval.top, oval.right, oval.bottom, startAngle, sweepAngle, useCenter, + paint); + } + + /** + * <p>Draw the specified arc, which will be scaled to fit inside the + * specified oval.</p> + * + * <p>If the start angle is negative or >= 360, the start angle is treated + * as start angle modulo 360.</p> + * + * <p>If the sweep angle is >= 360, then the oval is drawn + * completely. Note that this differs slightly from SkPath::arcTo, which + * treats the sweep angle modulo 360. If the sweep angle is negative, + * the sweep angle is treated as sweep angle modulo 360</p> + * + * <p>The arc is drawn clockwise. An angle of 0 degrees correspond to the + * geometric angle of 0 degrees (3 o'clock on a watch.)</p> + * + * @param startAngle Starting angle (in degrees) where the arc begins + * @param sweepAngle Sweep angle (in degrees) measured clockwise + * @param useCenter If true, include the center of the oval in the arc, and + close it if it is being stroked. This will draw a wedge + * @param paint The paint used to draw the arc + */ + public void drawArc(float left, float top, float right, float bottom, float startAngle, + float sweepAngle, boolean useCenter, @NonNull Paint paint) { + native_drawArc(mNativeCanvasWrapper, left, top, right, bottom, startAngle, sweepAngle, useCenter, paint.mNativePaint); } @@ -1082,7 +1181,7 @@ public class Canvas { * @param ry The y-radius of the oval used to round the corners * @param paint The paint used to draw the roundRect */ - public void drawRoundRect(RectF rect, float rx, float ry, Paint paint) { + public void drawRoundRect(@NonNull RectF rect, float rx, float ry, @NonNull Paint paint) { drawRoundRect(rect.left, rect.top, rect.right, rect.bottom, rx, ry, paint); } @@ -1095,8 +1194,8 @@ public class Canvas { * @param paint The paint used to draw the roundRect */ public void drawRoundRect(float left, float top, float right, float bottom, float rx, float ry, - Paint paint) { - native_drawRoundRect(mNativeCanvas, left, top, right, bottom, rx, ry, paint.mNativePaint); + @NonNull Paint paint) { + native_drawRoundRect(mNativeCanvasWrapper, left, top, right, bottom, rx, ry, paint.mNativePaint); } /** @@ -1106,8 +1205,8 @@ public class Canvas { * @param path The path to be drawn * @param paint The paint used to draw the path */ - public void drawPath(Path path, Paint paint) { - native_drawPath(mNativeCanvas, path.ni(), paint.mNativePaint); + public void drawPath(@NonNull Path path, @NonNull Paint paint) { + native_drawPath(mNativeCanvasWrapper, path.ni(), paint.mNativePaint); } /** @@ -1130,10 +1229,10 @@ public class Canvas { * @param patch The ninepatch object to render * @param dst The destination rectangle. * @param paint The paint to draw the bitmap with. may be null - * + * * @hide */ - public void drawPatch(NinePatch patch, Rect dst, Paint paint) { + public void drawPatch(@NonNull NinePatch patch, @NonNull Rect dst, @Nullable Paint paint) { patch.drawSoftware(this, dst, paint); } @@ -1146,14 +1245,14 @@ public class Canvas { * * @hide */ - public void drawPatch(NinePatch patch, RectF dst, Paint paint) { + public void drawPatch(@NonNull NinePatch patch, @NonNull RectF dst, @Nullable Paint paint) { patch.drawSoftware(this, dst, paint); } /** * Draw the specified bitmap, with its top/left corner at (x,y), using * the specified paint, transformed by the current matrix. - * + * * <p>Note: if the paint contains a maskfilter that generates a mask which * extends beyond the bitmap's original width/height (e.g. BlurMaskFilter), * then the bitmap will be drawn as if it were in a Shader with CLAMP mode. @@ -1163,15 +1262,15 @@ public class Canvas { * <p>If the bitmap and canvas have different densities, this function * will take care of automatically scaling the bitmap to draw at the * same density as the canvas. - * + * * @param bitmap The bitmap to be drawn * @param left The position of the left side of the bitmap being drawn * @param top The position of the top side of the bitmap being drawn * @param paint The paint used to draw the bitmap (may be null) */ - public void drawBitmap(Bitmap bitmap, float left, float top, Paint paint) { + public void drawBitmap(@NonNull Bitmap bitmap, float left, float top, @Nullable Paint paint) { throwIfCannotDraw(bitmap); - native_drawBitmap(mNativeCanvas, bitmap.ni(), left, top, + native_drawBitmap(mNativeCanvasWrapper, bitmap.ni(), left, top, paint != null ? paint.mNativePaint : 0, mDensity, mScreenDensity, bitmap.mDensity); } @@ -1179,7 +1278,7 @@ public class Canvas { * Draw the specified bitmap, scaling/translating automatically to fill * the destination rectangle. If the source rectangle is not null, it * specifies the subset of the bitmap to draw. - * + * * <p>Note: if the paint contains a maskfilter that generates a mask which * extends beyond the bitmap's original width/height (e.g. BlurMaskFilter), * then the bitmap will be drawn as if it were in a Shader with CLAMP mode. @@ -1190,19 +1289,20 @@ public class Canvas { * This is because the source and destination rectangle coordinate * spaces are in their respective densities, so must already have the * appropriate scaling factor applied. - * + * * @param bitmap The bitmap to be drawn * @param src May be null. The subset of the bitmap to be drawn * @param dst The rectangle that the bitmap will be scaled/translated * to fit into * @param paint May be null. The paint used to draw the bitmap */ - public void drawBitmap(Bitmap bitmap, Rect src, RectF dst, Paint paint) { + public void drawBitmap(@NonNull Bitmap bitmap, @Nullable Rect src, @NonNull RectF dst, + @Nullable Paint paint) { if (dst == null) { throw new NullPointerException(); } throwIfCannotDraw(bitmap); - native_drawBitmap(mNativeCanvas, bitmap.ni(), src, dst, + native_drawBitmap(mNativeCanvasWrapper, bitmap.ni(), src, dst, paint != null ? paint.mNativePaint : 0, mScreenDensity, bitmap.mDensity); } @@ -1210,7 +1310,7 @@ public class Canvas { * Draw the specified bitmap, scaling/translating automatically to fill * the destination rectangle. If the source rectangle is not null, it * specifies the subset of the bitmap to draw. - * + * * <p>Note: if the paint contains a maskfilter that generates a mask which * extends beyond the bitmap's original width/height (e.g. BlurMaskFilter), * then the bitmap will be drawn as if it were in a Shader with CLAMP mode. @@ -1221,22 +1321,23 @@ public class Canvas { * This is because the source and destination rectangle coordinate * spaces are in their respective densities, so must already have the * appropriate scaling factor applied. - * + * * @param bitmap The bitmap to be drawn * @param src May be null. The subset of the bitmap to be drawn * @param dst The rectangle that the bitmap will be scaled/translated * to fit into * @param paint May be null. The paint used to draw the bitmap */ - public void drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint) { + public void drawBitmap(@NonNull Bitmap bitmap, @Nullable Rect src, @NonNull Rect dst, + @Nullable Paint paint) { if (dst == null) { throw new NullPointerException(); } throwIfCannotDraw(bitmap); - native_drawBitmap(mNativeCanvas, bitmap.ni(), src, dst, + native_drawBitmap(mNativeCanvasWrapper, bitmap.ni(), src, dst, paint != null ? paint.mNativePaint : 0, mScreenDensity, bitmap.mDensity); } - + /** * Treat the specified array of colors as a bitmap, and draw it. This gives * the same result as first creating a bitmap from the array, and then @@ -1262,8 +1363,8 @@ public class Canvas { * and copies of pixel data. */ @Deprecated - public void drawBitmap(int[] colors, int offset, int stride, float x, float y, - int width, int height, boolean hasAlpha, Paint paint) { + public void drawBitmap(@NonNull int[] colors, int offset, int stride, float x, float y, + int width, int height, boolean hasAlpha, @Nullable Paint paint) { // check for valid input if (width < 0) { throw new IllegalArgumentException("width must be >= 0"); @@ -1285,7 +1386,7 @@ public class Canvas { return; } // punch down to native for the actual draw - native_drawBitmap(mNativeCanvas, colors, offset, stride, x, y, width, height, hasAlpha, + native_drawBitmap(mNativeCanvasWrapper, colors, offset, stride, x, y, width, height, hasAlpha, paint != null ? paint.mNativePaint : 0); } @@ -1298,8 +1399,8 @@ public class Canvas { * and copies of pixel data. */ @Deprecated - public void drawBitmap(int[] colors, int offset, int stride, int x, int y, - int width, int height, boolean hasAlpha, Paint paint) { + public void drawBitmap(@NonNull int[] colors, int offset, int stride, int x, int y, + int width, int height, boolean hasAlpha, @Nullable Paint paint) { // call through to the common float version drawBitmap(colors, offset, stride, (float)x, (float)y, width, height, hasAlpha, paint); @@ -1312,8 +1413,8 @@ public class Canvas { * @param matrix The matrix used to transform the bitmap when it is drawn * @param paint May be null. The paint used to draw the bitmap */ - public void drawBitmap(Bitmap bitmap, Matrix matrix, Paint paint) { - nativeDrawBitmapMatrix(mNativeCanvas, bitmap.ni(), matrix.ni(), + public void drawBitmap(@NonNull Bitmap bitmap, @NonNull Matrix matrix, @Nullable Paint paint) { + nativeDrawBitmapMatrix(mNativeCanvasWrapper, bitmap.ni(), matrix.ni(), paint != null ? paint.mNativePaint : 0); } @@ -1325,7 +1426,7 @@ public class Canvas { throw new ArrayIndexOutOfBoundsException(); } } - + /** * Draw the bitmap through the mesh, where mesh vertices are evenly * distributed across the bitmap. There are meshWidth+1 vertices across, and @@ -1352,8 +1453,9 @@ public class Canvas { * @param colorOffset Number of color elements to skip before drawing * @param paint May be null. The paint used to draw the bitmap */ - public void drawBitmapMesh(Bitmap bitmap, int meshWidth, int meshHeight, - float[] verts, int vertOffset, int[] colors, int colorOffset, Paint paint) { + public void drawBitmapMesh(@NonNull Bitmap bitmap, int meshWidth, int meshHeight, + @NonNull float[] verts, int vertOffset, @Nullable int[] colors, int colorOffset, + @Nullable Paint paint) { if ((meshWidth | meshHeight | vertOffset | colorOffset) < 0) { throw new ArrayIndexOutOfBoundsException(); } @@ -1367,7 +1469,7 @@ public class Canvas { // no mul by 2, since we need only 1 color per vertex checkRange(colors.length, colorOffset, count); } - nativeDrawBitmapMesh(mNativeCanvas, bitmap.ni(), meshWidth, meshHeight, + nativeDrawBitmapMesh(mNativeCanvasWrapper, bitmap.ni(), meshWidth, meshHeight, verts, vertOffset, colors, colorOffset, paint != null ? paint.mNativePaint : 0); } @@ -1376,7 +1478,7 @@ public class Canvas { TRIANGLES(0), TRIANGLE_STRIP(1), TRIANGLE_FAN(2); - + VertexMode(int nativeInt) { this.nativeInt = nativeInt; } @@ -1386,7 +1488,7 @@ public class Canvas { */ public final int nativeInt; } - + /** * Draw the array of vertices, interpreted as triangles (based on mode). The * verts array is required, and specifies the x,y pairs for each vertex. If @@ -1415,11 +1517,12 @@ public class Canvas { * @param indices If not null, array of indices to reference into the * vertex (texs, colors) array. * @param indexCount number of entries in the indices array (if not null). - * @param paint Specifies the shader to use if the texs array is non-null. + * @param paint Specifies the shader to use if the texs array is non-null. */ - public void drawVertices(VertexMode mode, int vertexCount, float[] verts, int vertOffset, - float[] texs, int texOffset, int[] colors, int colorOffset, - short[] indices, int indexOffset, int indexCount, Paint paint) { + public void drawVertices(@NonNull VertexMode mode, int vertexCount, @NonNull float[] verts, + int vertOffset, @Nullable float[] texs, int texOffset, @Nullable int[] colors, + int colorOffset, @Nullable short[] indices, int indexOffset, int indexCount, + @NonNull Paint paint) { checkRange(verts.length, vertOffset, vertexCount); if (texs != null) { checkRange(texs.length, texOffset, vertexCount); @@ -1430,7 +1533,7 @@ public class Canvas { if (indices != null) { checkRange(indices.length, indexOffset, indexCount); } - nativeDrawVertices(mNativeCanvas, mode.nativeInt, vertexCount, verts, + nativeDrawVertices(mNativeCanvasWrapper, mode.nativeInt, vertexCount, verts, vertOffset, texs, texOffset, colors, colorOffset, indices, indexOffset, indexCount, paint.mNativePaint); } @@ -1444,12 +1547,13 @@ public class Canvas { * @param y The y-coordinate of the origin of the text being drawn * @param paint The paint used for the text (e.g. color, size, style) */ - public void drawText(char[] text, int index, int count, float x, float y, Paint paint) { + public void drawText(@NonNull char[] text, int index, int count, float x, float y, + @NonNull Paint paint) { if ((index | count | (index + count) | (text.length - index - count)) < 0) { throw new IndexOutOfBoundsException(); } - native_drawText(mNativeCanvas, text, index, count, x, y, paint.mBidiFlags, + native_drawText(mNativeCanvasWrapper, text, index, count, x, y, paint.mBidiFlags, paint.mNativePaint, paint.mNativeTypeface); } @@ -1462,8 +1566,8 @@ public class Canvas { * @param y The y-coordinate of the origin of the text being drawn * @param paint The paint used for the text (e.g. color, size, style) */ - public void drawText(String text, float x, float y, Paint paint) { - native_drawText(mNativeCanvas, text, 0, text.length(), x, y, paint.mBidiFlags, + public void drawText(@NonNull String text, float x, float y, @NonNull Paint paint) { + native_drawText(mNativeCanvasWrapper, text, 0, text.length(), x, y, paint.mBidiFlags, paint.mNativePaint, paint.mNativeTypeface); } @@ -1478,11 +1582,12 @@ public class Canvas { * @param y The y-coordinate of the origin of the text being drawn * @param paint The paint used for the text (e.g. color, size, style) */ - public void drawText(String text, int start, int end, float x, float y, Paint paint) { + public void drawText(@NonNull String text, int start, int end, float x, float y, + @NonNull Paint paint) { if ((start | end | (end - start) | (text.length() - end)) < 0) { throw new IndexOutOfBoundsException(); } - native_drawText(mNativeCanvas, text, start, end, x, y, paint.mBidiFlags, + native_drawText(mNativeCanvasWrapper, text, start, end, x, y, paint.mBidiFlags, paint.mNativePaint, paint.mNativeTypeface); } @@ -1499,10 +1604,11 @@ public class Canvas { * @param y The y-coordinate of origin for where to draw the text * @param paint The paint used for the text (e.g. color, size, style) */ - public void drawText(CharSequence text, int start, int end, float x, float y, Paint paint) { + public void drawText(@NonNull CharSequence text, int start, int end, float x, float y, + @NonNull Paint paint) { if (text instanceof String || text instanceof SpannedString || text instanceof SpannableString) { - native_drawText(mNativeCanvas, text.toString(), start, end, x, y, + native_drawText(mNativeCanvasWrapper, text.toString(), start, end, x, y, paint.mBidiFlags, paint.mNativePaint, paint.mNativeTypeface); } else if (text instanceof GraphicsOperations) { ((GraphicsOperations) text).drawText(this, start, end, x, y, @@ -1510,7 +1616,7 @@ public class Canvas { } else { char[] buf = TemporaryBuffer.obtain(end - start); TextUtils.getChars(text, start, end, buf, 0); - native_drawText(mNativeCanvas, buf, 0, end - start, x, y, + native_drawText(mNativeCanvasWrapper, buf, 0, end - start, x, y, paint.mBidiFlags, paint.mNativePaint, paint.mNativeTypeface); TemporaryBuffer.recycle(buf); } @@ -1521,7 +1627,7 @@ public class Canvas { * bidi on the provided text, but renders it as a uniform right-to-left or * left-to-right run, as indicated by dir. Alignment of the text is as * determined by the Paint's TextAlign value. - * + * * @param text the text to render * @param index the start of the text to render * @param count the count of chars to render @@ -1532,13 +1638,12 @@ public class Canvas { * + count. * @param x the x position at which to draw the text * @param y the y position at which to draw the text - * @param dir the run direction, either {@link #DIRECTION_LTR} or - * {@link #DIRECTION_RTL}. + * @param isRtl whether the run is in RTL direction * @param paint the paint * @hide */ - public void drawTextRun(char[] text, int index, int count, int contextIndex, int contextCount, - float x, float y, int dir, Paint paint) { + public void drawTextRun(@NonNull char[] text, int index, int count, int contextIndex, + int contextCount, float x, float y, boolean isRtl, @NonNull Paint paint) { if (text == null) { throw new NullPointerException("text is null"); @@ -1549,12 +1654,9 @@ public class Canvas { if ((index | count | text.length - index - count) < 0) { throw new IndexOutOfBoundsException(); } - if (dir != DIRECTION_LTR && dir != DIRECTION_RTL) { - throw new IllegalArgumentException("unknown dir: " + dir); - } - native_drawTextRun(mNativeCanvas, text, index, count, - contextIndex, contextCount, x, y, dir, paint.mNativePaint, paint.mNativeTypeface); + native_drawTextRun(mNativeCanvasWrapper, text, index, count, + contextIndex, contextCount, x, y, isRtl, paint.mNativePaint, paint.mNativeTypeface); } /** @@ -1570,12 +1672,12 @@ public class Canvas { * position can be used for shaping context. * @param x the x position at which to draw the text * @param y the y position at which to draw the text - * @param dir the run direction, either 0 for LTR or 1 for RTL. + * @param isRtl whether the run is in RTL direction * @param paint the paint * @hide */ - public void drawTextRun(CharSequence text, int start, int end, int contextStart, int contextEnd, - float x, float y, int dir, Paint paint) { + public void drawTextRun(@NonNull CharSequence text, int start, int end, int contextStart, + int contextEnd, float x, float y, boolean isRtl, @NonNull Paint paint) { if (text == null) { throw new NullPointerException("text is null"); @@ -1587,22 +1689,20 @@ public class Canvas { throw new IndexOutOfBoundsException(); } - int flags = dir == 0 ? 0 : 1; - if (text instanceof String || text instanceof SpannedString || text instanceof SpannableString) { - native_drawTextRun(mNativeCanvas, text.toString(), start, end, - contextStart, contextEnd, x, y, flags, paint.mNativePaint, paint.mNativeTypeface); + native_drawTextRun(mNativeCanvasWrapper, text.toString(), start, end, + contextStart, contextEnd, x, y, isRtl, paint.mNativePaint, paint.mNativeTypeface); } else if (text instanceof GraphicsOperations) { ((GraphicsOperations) text).drawTextRun(this, start, end, - contextStart, contextEnd, x, y, flags, paint); + contextStart, contextEnd, x, y, isRtl, paint); } else { int contextLen = contextEnd - contextStart; int len = end - start; char[] buf = TemporaryBuffer.obtain(contextLen); TextUtils.getChars(text, contextStart, contextEnd, buf, 0); - native_drawTextRun(mNativeCanvas, buf, start - contextStart, len, - 0, contextLen, x, y, flags, paint.mNativePaint, paint.mNativeTypeface); + native_drawTextRun(mNativeCanvasWrapper, buf, start - contextStart, len, + 0, contextLen, x, y, isRtl, paint.mNativePaint, paint.mNativeTypeface); TemporaryBuffer.recycle(buf); } } @@ -1610,9 +1710,10 @@ public class Canvas { /** * Draw the text in the array, with each character's origin specified by * the pos array. - * + * * This method does not support glyph composition and decomposition and - * should therefore not be used to render complex scripts. + * should therefore not be used to render complex scripts. It also doesn't + * handle supplementary characters (eg emoji). * * @param text The text to be drawn * @param index The index of the first character to draw @@ -1622,31 +1723,31 @@ public class Canvas { * @param paint The paint used for the text (e.g. color, size, style) */ @Deprecated - public void drawPosText(char[] text, int index, int count, float[] pos, Paint paint) { + public void drawPosText(@NonNull char[] text, int index, int count, @NonNull float[] pos, + @NonNull Paint paint) { if (index < 0 || index + count > text.length || count*2 > pos.length) { throw new IndexOutOfBoundsException(); } - native_drawPosText(mNativeCanvas, text, index, count, pos, - paint.mNativePaint); + for (int i = 0; i < count; i++) { + drawText(text, index + i, 1, pos[i * 2], pos[i * 2 + 1], paint); + } } /** * Draw the text in the array, with each character's origin specified by * the pos array. - * + * * This method does not support glyph composition and decomposition and - * should therefore not be used to render complex scripts. + * should therefore not be used to render complex scripts. It also doesn't + * handle supplementary characters (eg emoji). * * @param text The text to be drawn * @param pos Array of [x,y] positions, used to position each character * @param paint The paint used for the text (e.g. color, size, style) */ @Deprecated - public void drawPosText(String text, float[] pos, Paint paint) { - if (text.length()*2 > pos.length) { - throw new ArrayIndexOutOfBoundsException(); - } - native_drawPosText(mNativeCanvas, text, pos, paint.mNativePaint); + public void drawPosText(@NonNull String text, @NonNull float[] pos, @NonNull Paint paint) { + drawPosText(text.toCharArray(), 0, text.length(), pos, paint); } /** @@ -1662,14 +1763,14 @@ public class Canvas { * the text * @param paint The paint used for the text (e.g. color, size, style) */ - public void drawTextOnPath(char[] text, int index, int count, Path path, - float hOffset, float vOffset, Paint paint) { + public void drawTextOnPath(@NonNull char[] text, int index, int count, @NonNull Path path, + float hOffset, float vOffset, @NonNull Paint paint) { if (index < 0 || index + count > text.length) { throw new ArrayIndexOutOfBoundsException(); } - native_drawTextOnPath(mNativeCanvas, text, index, count, + native_drawTextOnPath(mNativeCanvasWrapper, text, index, count, path.ni(), hOffset, vOffset, - paint.mBidiFlags, paint.mNativePaint); + paint.mBidiFlags, paint.mNativePaint, paint.mNativeTypeface); } /** @@ -1685,10 +1786,11 @@ public class Canvas { * the text * @param paint The paint used for the text (e.g. color, size, style) */ - public void drawTextOnPath(String text, Path path, float hOffset, float vOffset, Paint paint) { + public void drawTextOnPath(@NonNull String text, @NonNull Path path, float hOffset, + float vOffset, @NonNull Paint paint) { if (text.length() > 0) { - native_drawTextOnPath(mNativeCanvas, text, path.ni(), hOffset, vOffset, - paint.mBidiFlags, paint.mNativePaint); + native_drawTextOnPath(mNativeCanvasWrapper, text, path.ni(), hOffset, vOffset, + paint.mBidiFlags, paint.mNativePaint, paint.mNativeTypeface); } } @@ -1700,20 +1802,20 @@ public class Canvas { * <p> * <strong>Note:</strong> This forces the picture to internally call * {@link Picture#endRecording} in order to prepare for playback. - * + * * @param picture The picture to be drawn */ - public void drawPicture(Picture picture) { + public void drawPicture(@NonNull Picture picture) { picture.endRecording(); int restoreCount = save(); picture.draw(this); restoreToCount(restoreCount); } - + /** * Draw the picture, stretched to fit into the dst rectangle. */ - public void drawPicture(Picture picture, RectF dst) { + public void drawPicture(@NonNull Picture picture, @NonNull RectF dst) { save(); translate(dst.left, dst.top); if (picture.getWidth() > 0 && picture.getHeight() > 0) { @@ -1722,11 +1824,11 @@ public class Canvas { drawPicture(picture); restore(); } - + /** * Draw the picture, stretched to fit into the dst rectangle. */ - public void drawPicture(Picture picture, Rect dst) { + public void drawPicture(@NonNull Picture picture, @NonNull Rect dst) { save(); translate(dst.left, dst.top); if (picture.getWidth() > 0 && picture.getHeight() > 0) { @@ -1761,23 +1863,34 @@ public class Canvas { public static native void freeTextLayoutCaches(); private static native long initRaster(long nativeBitmapOrZero); - private static native void copyNativeCanvasState(long nativeSrcCanvas, - long nativeDstCanvas); - private static native int native_saveLayer(long nativeCanvas, - RectF bounds, - long nativePaint, - int layerFlags); + private static native long initCanvas(long canvasHandle); + private static native void native_setBitmap(long canvasHandle, + long bitmapHandle, + boolean copyState); + private static native boolean native_isOpaque(long canvasHandle); + private static native int native_getWidth(long canvasHandle); + private static native int native_getHeight(long canvasHandle); + + private static native int native_save(long canvasHandle, int saveFlags); private static native int native_saveLayer(long nativeCanvas, float l, float t, float r, float b, long nativePaint, int layerFlags); - private static native int native_saveLayerAlpha(long nativeCanvas, - RectF bounds, int alpha, - int layerFlags); private static native int native_saveLayerAlpha(long nativeCanvas, float l, float t, float r, float b, int alpha, int layerFlags); - + private static native void native_restore(long canvasHandle); + private static native void native_restoreToCount(long canvasHandle, + int saveCount); + private static native int native_getSaveCount(long canvasHandle); + + private static native void native_translate(long canvasHandle, + float dx, float dy); + private static native void native_scale(long canvasHandle, + float sx, float sy); + private static native void native_rotate(long canvasHandle, float degrees); + private static native void native_skew(long canvasHandle, + float sx, float sy); private static native void native_concat(long nativeCanvas, long nativeMatrix); private static native void native_setMatrix(long nativeCanvas, @@ -1799,8 +1912,6 @@ public class Canvas { private static native void native_getCTM(long nativeCanvas, long nativeMatrix); private static native boolean native_quickReject(long nativeCanvas, - RectF rect); - private static native boolean native_quickReject(long nativeCanvas, long nativePath); private static native boolean native_quickReject(long nativeCanvas, float left, float top, @@ -1814,23 +1925,29 @@ public class Canvas { int mode); private static native void native_drawPaint(long nativeCanvas, long nativePaint); + private static native void native_drawPoint(long canvasHandle, float x, float y, + long paintHandle); + private static native void native_drawPoints(long canvasHandle, float[] pts, + int offset, int count, + long paintHandle); private static native void native_drawLine(long nativeCanvas, float startX, float startY, float stopX, float stopY, long nativePaint); - private static native void native_drawRect(long nativeCanvas, RectF rect, - long nativePaint); + private static native void native_drawLines(long canvasHandle, float[] pts, + int offset, int count, + long paintHandle); private static native void native_drawRect(long nativeCanvas, float left, float top, float right, float bottom, long nativePaint); - private static native void native_drawOval(long nativeCanvas, RectF oval, - long nativePaint); + private static native void native_drawOval(long nativeCanvas, float left, float top, + float right, float bottom, long nativePaint); private static native void native_drawCircle(long nativeCanvas, float cx, float cy, float radius, long nativePaint); - private static native void native_drawArc(long nativeCanvas, RectF oval, - float startAngle, float sweep, - boolean useCenter, + private static native void native_drawArc(long nativeCanvas, float left, float top, + float right, float bottom, + float startAngle, float sweep, boolean useCenter, long nativePaint); private static native void native_drawRoundRect(long nativeCanvas, float left, float top, float right, float bottom, @@ -1886,29 +2003,22 @@ public class Canvas { private static native void native_drawTextRun(long nativeCanvas, String text, int start, int end, int contextStart, int contextEnd, - float x, float y, int flags, long nativePaint, long nativeTypeface); + float x, float y, boolean isRtl, long nativePaint, long nativeTypeface); private static native void native_drawTextRun(long nativeCanvas, char[] text, int start, int count, int contextStart, int contextCount, - float x, float y, int flags, long nativePaint, long nativeTypeface); - - private static native void native_drawPosText(long nativeCanvas, - char[] text, int index, - int count, float[] pos, - long nativePaint); - private static native void native_drawPosText(long nativeCanvas, - String text, float[] pos, - long nativePaint); + float x, float y, boolean isRtl, long nativePaint, long nativeTypeface); + private static native void native_drawTextOnPath(long nativeCanvas, char[] text, int index, int count, long nativePath, float hOffset, float vOffset, int bidiFlags, - long nativePaint); + long nativePaint, long nativeTypeface); private static native void native_drawTextOnPath(long nativeCanvas, String text, long nativePath, float hOffset, float vOffset, - int flags, long nativePaint); + int flags, long nativePaint, long nativeTypeface); private static native void finalizer(long nativeCanvas); } diff --git a/graphics/java/android/graphics/NinePatch.java b/graphics/java/android/graphics/NinePatch.java index 6ff5f4f..befac92 100644 --- a/graphics/java/android/graphics/NinePatch.java +++ b/graphics/java/android/graphics/NinePatch.java @@ -164,12 +164,12 @@ public class NinePatch { } void drawSoftware(Canvas canvas, RectF location, Paint paint) { - nativeDraw(canvas.getNativeCanvas(), location, mBitmap.ni(), mNativeChunk, + nativeDraw(canvas.getNativeCanvasWrapper(), location, mBitmap.ni(), mNativeChunk, paint != null ? paint.mNativePaint : 0, canvas.mDensity, mBitmap.mDensity); } void drawSoftware(Canvas canvas, Rect location, Paint paint) { - nativeDraw(canvas.getNativeCanvas(), location, mBitmap.ni(), mNativeChunk, + nativeDraw(canvas.getNativeCanvasWrapper(), location, mBitmap.ni(), mNativeChunk, paint != null ? paint.mNativePaint : 0, canvas.mDensity, mBitmap.mDensity); } diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java index 4268a24..17ce026 100644 --- a/graphics/java/android/graphics/Paint.java +++ b/graphics/java/android/graphics/Paint.java @@ -56,7 +56,7 @@ public class Paint { * @hide */ public int mBidiFlags = BIDI_DEFAULT_LTR; - + static final Style[] sStyleArray = { Style.FILL, Style.STROKE, Style.FILL_AND_STROKE }; @@ -202,14 +202,14 @@ public class Paint { /** * Bidi flag to set LTR paragraph direction. - * + * * @hide */ public static final int BIDI_LTR = 0x0; /** * Bidi flag to set RTL paragraph direction. - * + * * @hide */ public static final int BIDI_RTL = 0x1; @@ -217,7 +217,7 @@ public class Paint { /** * Bidi flag to detect paragraph direction via heuristics, defaulting to * LTR. - * + * * @hide */ public static final int BIDI_DEFAULT_LTR = 0x2; @@ -225,21 +225,21 @@ public class Paint { /** * Bidi flag to detect paragraph direction via heuristics, defaulting to * RTL. - * + * * @hide */ public static final int BIDI_DEFAULT_RTL = 0x3; /** * Bidi flag to override direction to all LTR (ignore bidi). - * + * * @hide */ public static final int BIDI_FORCE_LTR = 0x4; /** * Bidi flag to override direction to all RTL (ignore bidi). - * + * * @hide */ public static final int BIDI_FORCE_RTL = 0x5; @@ -331,7 +331,7 @@ public class Paint { * either FILL or STROKE. */ FILL_AND_STROKE (2); - + Style(int nativeInt) { this.nativeInt = nativeInt; } @@ -357,7 +357,7 @@ public class Paint { * of the path. */ SQUARE (2); - + private Cap(int nativeInt) { this.nativeInt = nativeInt; } @@ -381,7 +381,7 @@ public class Paint { * The outer edges of a join meet with a straight line */ BEVEL (2); - + private Join(int nativeInt) { this.nativeInt = nativeInt; } @@ -405,7 +405,7 @@ public class Paint { * The text is drawn to the left of the x,y origin */ RIGHT (2); - + private Align(int nativeInt) { this.nativeInt = nativeInt; } @@ -418,7 +418,7 @@ public class Paint { public Paint() { this(0); } - + /** * Create a new paint with the specified flags. Use setFlags() to change * these after the paint is created. @@ -475,7 +475,7 @@ public class Paint { setTextLocale(Locale.getDefault()); setElegantTextHeight(false); } - + /** * Copy the fields from src into this paint. This is equivalent to calling * get() on all of the src fields, and calling the corresponding set() @@ -529,7 +529,7 @@ public class Paint { /** * Return the bidi flags on the paint. - * + * * @return the bidi flags on the paint * @hide */ @@ -552,7 +552,7 @@ public class Paint { /** * Return the paint's flags. Use the Flag enum to test flag values. - * + * * @return the paint's flags (see enums ending in _Flag for bit masks) */ public native int getFlags(); @@ -587,7 +587,7 @@ public class Paint { public final boolean isAntiAlias() { return (getFlags() & ANTI_ALIAS_FLAG) != 0; } - + /** * Helper for setFlags(), setting or clearing the ANTI_ALIAS_FLAG bit * AntiAliasing smooths out the edges of what is being drawn, but is has @@ -597,7 +597,7 @@ public class Paint { * @param aa true to set the antialias bit in the flags, false to clear it */ public native void setAntiAlias(boolean aa); - + /** * Helper for getFlags(), returning true if DITHER_FLAG bit is set * Dithering affects how colors that are higher precision than the device @@ -611,7 +611,7 @@ public class Paint { public final boolean isDither() { return (getFlags() & DITHER_FLAG) != 0; } - + /** * Helper for setFlags(), setting or clearing the DITHER_FLAG bit * Dithering affects how colors that are higher precision than the device @@ -623,7 +623,7 @@ public class Paint { * @param dither true to set the dithering bit in flags, false to clear it */ public native void setDither(boolean dither); - + /** * Helper for getFlags(), returning true if LINEAR_TEXT_FLAG bit is set * @@ -649,7 +649,7 @@ public class Paint { public final boolean isSubpixelText() { return (getFlags() & SUBPIXEL_TEXT_FLAG) != 0; } - + /** * Helper for setFlags(), setting or clearing the SUBPIXEL_TEXT_FLAG bit * @@ -657,7 +657,7 @@ public class Paint { * flags, false to clear it. */ public native void setSubpixelText(boolean subpixelText); - + /** * Helper for getFlags(), returning true if UNDERLINE_TEXT_FLAG bit is set * @@ -708,7 +708,7 @@ public class Paint { * flags, false to clear it. */ public native void setFakeBoldText(boolean fakeBoldText); - + /** * Whether or not the bitmap filter is activated. * Filtering affects the sampling of bitmaps when they are transformed. @@ -720,13 +720,13 @@ public class Paint { public final boolean isFilterBitmap() { return (getFlags() & FILTER_BITMAP_FLAG) != 0; } - + /** * Helper for setFlags(), setting or clearing the FILTER_BITMAP_FLAG bit. * Filtering affects the sampling of bitmaps when they are transformed. * Filtering does not affect how the colors in the bitmap are converted into * device pixels. That is dependent on dithering and xfermodes. - * + * * @param filter true to set the FILTER_BITMAP_FLAG bit in the paint's * flags, false to clear it. */ @@ -773,7 +773,7 @@ public class Paint { * @param color The new color (including alpha) to set in the paint. */ public native void setColor(int color); - + /** * Helper to getColor() that just returns the color's alpha value. This is * the same as calling getColor() >>> 24. It always returns a value between @@ -1285,7 +1285,7 @@ public class Paint { */ public static class FontMetrics { /** - * The maximum distance above the baseline for the tallest glyph in + * The maximum distance above the baseline for the tallest glyph in * the font at a given text size. */ public float top; @@ -1298,7 +1298,7 @@ public class Paint { */ public float descent; /** - * The maximum distance below the baseline for the lowest glyph in + * The maximum distance below the baseline for the lowest glyph in * the font at a given text size. */ public float bottom; @@ -1307,7 +1307,7 @@ public class Paint { */ public float leading; } - + /** * Return the font's recommended interline spacing, given the Paint's * settings for typeface, textSize, etc. If metrics is not null, return the @@ -1318,7 +1318,7 @@ public class Paint { * @return the font's recommended interline spacing. */ public native float getFontMetrics(FontMetrics metrics); - + /** * Allocates a new FontMetrics object, and then calls getFontMetrics(fm) * with it, returning the object. @@ -1328,7 +1328,7 @@ public class Paint { getFontMetrics(fm); return fm; } - + /** * Convenience method for callers that want to have FontMetrics values as * integers. @@ -1339,7 +1339,7 @@ public class Paint { public int descent; public int bottom; public int leading; - + @Override public String toString() { return "FontMetricsInt: top=" + top + " ascent=" + ascent + " descent=" + descent + " bottom=" + bottom + @@ -1364,7 +1364,7 @@ public class Paint { getFontMetricsInt(fm); return fm; } - + /** * Return the recommend line spacing based on the current typeface and * text size. @@ -1407,7 +1407,7 @@ public class Paint { } private native float native_measureText(char[] text, int index, int count, int bidiFlags); - + /** * Return the width of the text. * @@ -1439,7 +1439,7 @@ public class Paint { } private native float native_measureText(String text, int start, int end, int bidiFlags); - + /** * Return the width of the text. * @@ -1466,7 +1466,7 @@ public class Paint { } private native float native_measureText(String text, int bidiFlags); - + /** * Return the width of the text. * @@ -1503,7 +1503,7 @@ public class Paint { TemporaryBuffer.recycle(buf); return result; } - + /** * Measure the text, stopping early if the measured width exceeds maxWidth. * Return the number of chars that were measured, and if measuredWidth is @@ -1532,20 +1532,22 @@ public class Paint { return 0; } if (!mHasCompatScaling) { - return native_breakText(text, index, count, maxWidth, mBidiFlags, measuredWidth); + return native_breakText(mNativePaint, mNativeTypeface, text, index, count, maxWidth, + mBidiFlags, measuredWidth); } final float oldSize = getTextSize(); - setTextSize(oldSize*mCompatScaling); - int res = native_breakText(text, index, count, maxWidth*mCompatScaling, mBidiFlags, - measuredWidth); + setTextSize(oldSize * mCompatScaling); + int res = native_breakText(mNativePaint, mNativeTypeface, text, index, count, + maxWidth * mCompatScaling, mBidiFlags, measuredWidth); setTextSize(oldSize); if (measuredWidth != null) measuredWidth[0] *= mInvCompatScaling; return res; } - private native int native_breakText(char[] text, int index, int count, - float maxWidth, int bidiFlags, float[] measuredWidth); + private static native int native_breakText(long native_object, long native_typeface, + char[] text, int index, int count, + float maxWidth, int bidiFlags, float[] measuredWidth); /** * Measure the text, stopping early if the measured width exceeds maxWidth. @@ -1622,19 +1624,21 @@ public class Paint { return 0; } if (!mHasCompatScaling) { - return native_breakText(text, measureForwards, maxWidth, mBidiFlags, measuredWidth); + return native_breakText(mNativePaint, mNativeTypeface, text, measureForwards, + maxWidth, mBidiFlags, measuredWidth); } final float oldSize = getTextSize(); setTextSize(oldSize*mCompatScaling); - int res = native_breakText(text, measureForwards, maxWidth*mCompatScaling, mBidiFlags, - measuredWidth); + int res = native_breakText(mNativePaint, mNativeTypeface, text, measureForwards, + maxWidth*mCompatScaling, mBidiFlags, measuredWidth); setTextSize(oldSize); if (measuredWidth != null) measuredWidth[0] *= mInvCompatScaling; return res; } - private native int native_breakText(String text, boolean measureForwards, + private static native int native_breakText(long native_object, long native_typeface, + String text, boolean measureForwards, float maxWidth, int bidiFlags, float[] measuredWidth); /** @@ -1738,7 +1742,7 @@ public class Paint { if (end - start > widths.length) { throw new ArrayIndexOutOfBoundsException(); } - + if (text.length() == 0 || start == end) { return 0; } @@ -1755,7 +1759,7 @@ public class Paint { } return res; } - + /** * Return the advance widths for the characters in the string. * @@ -1816,15 +1820,12 @@ public class Paint { * @hide */ public float getTextRunAdvances(char[] chars, int index, int count, - int contextIndex, int contextCount, int flags, float[] advances, + int contextIndex, int contextCount, boolean isRtl, float[] advances, int advancesIndex) { if (chars == null) { throw new IllegalArgumentException("text cannot be null"); } - if (flags != DIRECTION_LTR && flags != DIRECTION_RTL) { - throw new IllegalArgumentException("unknown flags value: " + flags); - } if ((index | count | contextIndex | contextCount | advancesIndex | (index - contextIndex) | (contextCount - count) | ((contextIndex + contextCount) - (index + count)) @@ -1839,13 +1840,13 @@ public class Paint { } if (!mHasCompatScaling) { return native_getTextRunAdvances(mNativePaint, mNativeTypeface, chars, index, count, - contextIndex, contextCount, flags, advances, advancesIndex); + contextIndex, contextCount, isRtl, advances, advancesIndex); } final float oldSize = getTextSize(); setTextSize(oldSize * mCompatScaling); float res = native_getTextRunAdvances(mNativePaint, mNativeTypeface, chars, index, count, - contextIndex, contextCount, flags, advances, advancesIndex); + contextIndex, contextCount, isRtl, advances, advancesIndex); setTextSize(oldSize); if (advances != null) { @@ -1864,7 +1865,7 @@ public class Paint { * @hide */ public float getTextRunAdvances(CharSequence text, int start, int end, - int contextStart, int contextEnd, int flags, float[] advances, + int contextStart, int contextEnd, boolean isRtl, float[] advances, int advancesIndex) { if (text == null) { @@ -1880,16 +1881,16 @@ public class Paint { if (text instanceof String) { return getTextRunAdvances((String) text, start, end, - contextStart, contextEnd, flags, advances, advancesIndex); + contextStart, contextEnd, isRtl, advances, advancesIndex); } if (text instanceof SpannedString || text instanceof SpannableString) { return getTextRunAdvances(text.toString(), start, end, - contextStart, contextEnd, flags, advances, advancesIndex); + contextStart, contextEnd, isRtl, advances, advancesIndex); } if (text instanceof GraphicsOperations) { return ((GraphicsOperations) text).getTextRunAdvances(start, end, - contextStart, contextEnd, flags, advances, advancesIndex, this); + contextStart, contextEnd, isRtl, advances, advancesIndex, this); } if (text.length() == 0 || end == start) { return 0f; @@ -1900,7 +1901,7 @@ public class Paint { char[] buf = TemporaryBuffer.obtain(contextLen); TextUtils.getChars(text, contextStart, contextEnd, buf, 0); float result = getTextRunAdvances(buf, start - contextStart, len, - 0, contextLen, flags, advances, advancesIndex); + 0, contextLen, isRtl, advances, advancesIndex); TemporaryBuffer.recycle(buf); return result; } @@ -1937,8 +1938,7 @@ public class Paint { * must be <= start * @param contextEnd the index past the last character to use for shaping context, * must be >= end - * @param flags the flags to control the advances, either {@link #DIRECTION_LTR} - * or {@link #DIRECTION_RTL} + * @param isRtl whether the run is in RTL direction * @param advances array to receive the advances, must have room for all advances, * can be null if only total advance is needed * @param advancesIndex the position in advances at which to put the @@ -1948,14 +1948,11 @@ public class Paint { * @hide */ public float getTextRunAdvances(String text, int start, int end, int contextStart, - int contextEnd, int flags, float[] advances, int advancesIndex) { + int contextEnd, boolean isRtl, float[] advances, int advancesIndex) { if (text == null) { throw new IllegalArgumentException("text cannot be null"); } - if (flags != DIRECTION_LTR && flags != DIRECTION_RTL) { - throw new IllegalArgumentException("unknown flags value: " + flags); - } if ((start | end | contextStart | contextEnd | advancesIndex | (end - start) | (start - contextStart) | (contextEnd - end) | (text.length() - contextEnd) @@ -1970,13 +1967,13 @@ public class Paint { if (!mHasCompatScaling) { return native_getTextRunAdvances(mNativePaint, mNativeTypeface, text, start, end, - contextStart, contextEnd, flags, advances, advancesIndex); + contextStart, contextEnd, isRtl, advances, advancesIndex); } final float oldSize = getTextSize(); setTextSize(oldSize * mCompatScaling); float totalAdvance = native_getTextRunAdvances(mNativePaint, mNativeTypeface, text, start, end, - contextStart, contextEnd, flags, advances, advancesIndex); + contextStart, contextEnd, isRtl, advances, advancesIndex); setTextSize(oldSize); if (advances != null) { @@ -2005,7 +2002,7 @@ public class Paint { * @param text the text * @param contextStart the start of the context * @param contextLength the length of the context - * @param flags either {@link #DIRECTION_RTL} or {@link #DIRECTION_LTR} + * @param dir either {@link #DIRECTION_RTL} or {@link #DIRECTION_LTR} * @param offset the cursor position to move from * @param cursorOpt how to move the cursor, one of {@link #CURSOR_AFTER}, * {@link #CURSOR_AT_OR_AFTER}, {@link #CURSOR_BEFORE}, @@ -2014,7 +2011,7 @@ public class Paint { * @hide */ public int getTextRunCursor(char[] text, int contextStart, int contextLength, - int flags, int offset, int cursorOpt) { + int dir, int offset, int cursorOpt) { int contextEnd = contextStart + contextLength; if (((contextStart | contextEnd | offset | (contextEnd - contextStart) | (offset - contextStart) | (contextEnd - offset) @@ -2024,7 +2021,7 @@ public class Paint { } return native_getTextRunCursor(mNativePaint, text, - contextStart, contextLength, flags, offset, cursorOpt); + contextStart, contextLength, dir, offset, cursorOpt); } /** @@ -2045,7 +2042,7 @@ public class Paint { * @param text the text * @param contextStart the start of the context * @param contextEnd the end of the context - * @param flags either {@link #DIRECTION_RTL} or {@link #DIRECTION_LTR} + * @param dir either {@link #DIRECTION_RTL} or {@link #DIRECTION_LTR} * @param offset the cursor position to move from * @param cursorOpt how to move the cursor, one of {@link #CURSOR_AFTER}, * {@link #CURSOR_AT_OR_AFTER}, {@link #CURSOR_BEFORE}, @@ -2054,22 +2051,22 @@ public class Paint { * @hide */ public int getTextRunCursor(CharSequence text, int contextStart, - int contextEnd, int flags, int offset, int cursorOpt) { + int contextEnd, int dir, int offset, int cursorOpt) { if (text instanceof String || text instanceof SpannedString || text instanceof SpannableString) { return getTextRunCursor(text.toString(), contextStart, contextEnd, - flags, offset, cursorOpt); + dir, offset, cursorOpt); } if (text instanceof GraphicsOperations) { return ((GraphicsOperations) text).getTextRunCursor( - contextStart, contextEnd, flags, offset, cursorOpt, this); + contextStart, contextEnd, dir, offset, cursorOpt, this); } int contextLen = contextEnd - contextStart; char[] buf = TemporaryBuffer.obtain(contextLen); TextUtils.getChars(text, contextStart, contextEnd, buf, 0); - int result = getTextRunCursor(buf, 0, contextLen, flags, offset - contextStart, cursorOpt); + int result = getTextRunCursor(buf, 0, contextLen, dir, offset - contextStart, cursorOpt); TemporaryBuffer.recycle(buf); return result; } @@ -2092,7 +2089,7 @@ public class Paint { * @param text the text * @param contextStart the start of the context * @param contextEnd the end of the context - * @param flags either {@link #DIRECTION_RTL} or {@link #DIRECTION_LTR} + * @param dir either {@link #DIRECTION_RTL} or {@link #DIRECTION_LTR} * @param offset the cursor position to move from * @param cursorOpt how to move the cursor, one of {@link #CURSOR_AFTER}, * {@link #CURSOR_AT_OR_AFTER}, {@link #CURSOR_BEFORE}, @@ -2101,7 +2098,7 @@ public class Paint { * @hide */ public int getTextRunCursor(String text, int contextStart, int contextEnd, - int flags, int offset, int cursorOpt) { + int dir, int offset, int cursorOpt) { if (((contextStart | contextEnd | offset | (contextEnd - contextStart) | (offset - contextStart) | (contextEnd - offset) | (text.length() - contextEnd) | cursorOpt) < 0) @@ -2110,7 +2107,7 @@ public class Paint { } return native_getTextRunCursor(mNativePaint, text, - contextStart, contextEnd, flags, offset, cursorOpt); + contextStart, contextEnd, dir, offset, cursorOpt); } /** @@ -2156,7 +2153,7 @@ public class Paint { native_getTextPath(mNativePaint, mNativeTypeface, mBidiFlags, text, start, end, x, y, path.ni()); } - + /** * Return in bounds (allocated by the caller) the smallest rectangle that * encloses all of the characters, with an implied origin at (0,0). @@ -2176,7 +2173,7 @@ public class Paint { } nativeGetStringBounds(mNativePaint, mNativeTypeface, text, start, end, mBidiFlags, bounds); } - + /** * Return in bounds (allocated by the caller) the smallest rectangle that * encloses all of the characters, with an implied origin at (0,0). @@ -2197,7 +2194,7 @@ public class Paint { nativeGetCharArrayBounds(mNativePaint, mNativeTypeface, text, index, count, mBidiFlags, bounds); } - + @Override protected void finalize() throws Throwable { try { @@ -2252,15 +2249,15 @@ public class Paint { private static native float native_getTextRunAdvances(long native_object, long native_typeface, char[] text, int index, int count, int contextIndex, int contextCount, - int flags, float[] advances, int advancesIndex); + boolean isRtl, float[] advances, int advancesIndex); private static native float native_getTextRunAdvances(long native_object, long native_typeface, String text, int start, int end, int contextStart, int contextEnd, - int flags, float[] advances, int advancesIndex); + boolean isRtl, float[] advances, int advancesIndex); private native int native_getTextRunCursor(long native_object, char[] text, - int contextStart, int contextLength, int flags, int offset, int cursorOpt); + int contextStart, int contextLength, int dir, int offset, int cursorOpt); private native int native_getTextRunCursor(long native_object, String text, - int contextStart, int contextEnd, int flags, int offset, int cursorOpt); + int contextStart, int contextEnd, int dir, int offset, int cursorOpt); private static native void native_getTextPath(long native_object, long native_typeface, int bidiFlags, char[] text, int index, int count, float x, float y, long path); diff --git a/graphics/java/android/graphics/Path.java b/graphics/java/android/graphics/Path.java index c600f47..c40a66d 100644 --- a/graphics/java/android/graphics/Path.java +++ b/graphics/java/android/graphics/Path.java @@ -62,7 +62,7 @@ public class Path { } mNativePath = init2(valNative); } - + /** * Clear any lines and curves from the path, making it empty. * This does NOT change the fill-type setting. @@ -205,7 +205,7 @@ public class Path { * Same as {@link #EVEN_ODD}, but draws outside of the path, rather than inside. */ INVERSE_EVEN_ODD(3); - + FillType(int ni) { nativeInt = ni; } @@ -425,7 +425,7 @@ public class Path { * the path is different from the path's current last point, then an * automatic lineTo() is added to connect the current contour to the * start of the arc. However, if the path is empty, then we call moveTo() - * with the first point of the arc. The sweep angle is tread mod 360. + * with the first point of the arc. * * @param oval The bounds of oval defining shape and size of the arc * @param startAngle Starting angle (in degrees) where the arc begins @@ -435,10 +435,9 @@ public class Path { */ public void arcTo(RectF oval, float startAngle, float sweepAngle, boolean forceMoveTo) { - isSimplePath = false; - native_arcTo(mNativePath, oval, startAngle, sweepAngle, forceMoveTo); + arcTo(oval.left, oval.top, oval.right, oval.bottom, startAngle, sweepAngle, forceMoveTo); } - + /** * Append the specified arc to the path as a new contour. If the start of * the path is different from the path's current last point, then an @@ -451,10 +450,27 @@ public class Path { * @param sweepAngle Sweep angle (in degrees) measured clockwise */ public void arcTo(RectF oval, float startAngle, float sweepAngle) { + arcTo(oval.left, oval.top, oval.right, oval.bottom, startAngle, sweepAngle, false); + } + + /** + * Append the specified arc to the path as a new contour. If the start of + * the path is different from the path's current last point, then an + * automatic lineTo() is added to connect the current contour to the + * start of the arc. However, if the path is empty, then we call moveTo() + * with the first point of the arc. + * + * @param startAngle Starting angle (in degrees) where the arc begins + * @param sweepAngle Sweep angle (in degrees) measured clockwise, treated + * mod 360. + * @param forceMoveTo If true, always begin a new contour with the arc + */ + public void arcTo(float left, float top, float right, float bottom, float startAngle, + float sweepAngle, boolean forceMoveTo) { isSimplePath = false; - native_arcTo(mNativePath, oval, startAngle, sweepAngle, false); + native_arcTo(mNativePath, left, top, right, bottom, startAngle, sweepAngle, forceMoveTo); } - + /** * Close the current contour. If the current point is not equal to the * first point of the contour, a line segment is automatically added. @@ -473,13 +489,13 @@ public class Path { CW (1), // must match enum in SkPath.h /** counter-clockwise */ CCW (2); // must match enum in SkPath.h - + Direction(int ni) { nativeInt = ni; } final int nativeInt; } - + private void detectSimplePath(float left, float top, float right, float bottom, Direction dir) { if (mLastDirection == null) { mLastDirection = dir; @@ -557,11 +573,19 @@ public class Path { * @param sweepAngle Sweep angle (in degrees) measured clockwise */ public void addArc(RectF oval, float startAngle, float sweepAngle) { - if (oval == null) { - throw new NullPointerException("need oval parameter"); - } + addArc(oval.left, oval.top, oval.right, oval.bottom, startAngle, sweepAngle); + } + + /** + * Add the specified arc to the path as a new contour. + * + * @param startAngle Starting angle (in degrees) where the arc begins + * @param sweepAngle Sweep angle (in degrees) measured clockwise + */ + public void addArc(float left, float top, float right, float bottom, float startAngle, + float sweepAngle) { isSimplePath = false; - native_addArc(mNativePath, oval, startAngle, sweepAngle); + native_addArc(mNativePath, left, top, right, bottom, startAngle, sweepAngle); } /** @@ -573,13 +597,22 @@ public class Path { * @param dir The direction to wind the round-rectangle's contour */ public void addRoundRect(RectF rect, float rx, float ry, Direction dir) { - if (rect == null) { - throw new NullPointerException("need rect parameter"); - } + addRoundRect(rect.left, rect.top, rect.right, rect.bottom, rx, ry, dir); + } + + /** + * Add a closed round-rectangle contour to the path + * + * @param rx The x-radius of the rounded corners on the round-rectangle + * @param ry The y-radius of the rounded corners on the round-rectangle + * @param dir The direction to wind the round-rectangle's contour + */ + public void addRoundRect(float left, float top, float right, float bottom, float rx, float ry, + Direction dir) { isSimplePath = false; - native_addRoundRect(mNativePath, rect, rx, ry, dir.nativeInt); + native_addRoundRect(mNativePath, left, top, right, bottom, rx, ry, dir.nativeInt); } - + /** * Add a closed round-rectangle contour to the path. Each corner receives * two radius values [X, Y]. The corners are ordered top-left, top-right, @@ -593,13 +626,26 @@ public class Path { if (rect == null) { throw new NullPointerException("need rect parameter"); } + addRoundRect(rect.left, rect.top, rect.right, rect.bottom, radii, dir); + } + + /** + * Add a closed round-rectangle contour to the path. Each corner receives + * two radius values [X, Y]. The corners are ordered top-left, top-right, + * bottom-right, bottom-left + * + * @param radii Array of 8 values, 4 pairs of [X,Y] radii + * @param dir The direction to wind the round-rectangle's contour + */ + public void addRoundRect(float left, float top, float right, float bottom, float[] radii, + Direction dir) { if (radii.length < 8) { throw new ArrayIndexOutOfBoundsException("radii[] needs 8 values"); } isSimplePath = false; - native_addRoundRect(mNativePath, rect, radii, dir.nativeInt); + native_addRoundRect(mNativePath, left, top, right, bottom, radii, dir.nativeInt); } - + /** * Add a copy of src to the path, offset by (dx,dy) * @@ -755,19 +801,24 @@ public class Path { float x2, float y2, float x3, float y3); private static native void native_rCubicTo(long nPath, float x1, float y1, float x2, float y2, float x3, float y3); - private static native void native_arcTo(long nPath, RectF oval, - float startAngle, float sweepAngle, boolean forceMoveTo); + private static native void native_arcTo(long nPath, float left, float top, + float right, float bottom, float startAngle, + float sweepAngle, boolean forceMoveTo); private static native void native_close(long nPath); private static native void native_addRect(long nPath, float left, float top, float right, float bottom, int dir); private static native void native_addOval(long nPath, float left, float top, float right, float bottom, int dir); private static native void native_addCircle(long nPath, float x, float y, float radius, int dir); - private static native void native_addArc(long nPath, RectF oval, - float startAngle, float sweepAngle); - private static native void native_addRoundRect(long nPath, RectF rect, + private static native void native_addArc(long nPath, float left, float top, + float right, float bottom, + float startAngle, float sweepAngle); + private static native void native_addRoundRect(long nPath, float left, float top, + float right, float bottom, float rx, float ry, int dir); - private static native void native_addRoundRect(long nPath, RectF r, float[] radii, int dir); + private static native void native_addRoundRect(long nPath, float left, float top, + float right, float bottom, + float[] radii, int dir); private static native void native_addPath(long nPath, long src, float dx, float dy); private static native void native_addPath(long nPath, long src); private static native void native_addPath(long nPath, long src, long matrix); diff --git a/graphics/java/android/graphics/Picture.java b/graphics/java/android/graphics/Picture.java index a16c099..5aa7c6a 100644 --- a/graphics/java/android/graphics/Picture.java +++ b/graphics/java/android/graphics/Picture.java @@ -31,18 +31,13 @@ public class Picture { private Canvas mRecordingCanvas; private final long mNativePicture; - /** - * @hide - */ - public final boolean createdFromStream; - private static final int WORKING_STREAM_STORAGE = 16 * 1024; /** * Creates an empty picture that is ready to record. */ public Picture() { - this(nativeConstructor(0), false); + this(nativeConstructor(0)); } /** @@ -51,9 +46,25 @@ public class Picture { * changes will not be reflected in this picture. */ public Picture(Picture src) { - this(nativeConstructor(src != null ? src.mNativePicture : 0), false); + this(nativeConstructor(src != null ? src.mNativePicture : 0)); + } + + private Picture(long nativePicture) { + if (nativePicture == 0) { + throw new RuntimeException(); + } + mNativePicture = nativePicture; + } + + @Override + protected void finalize() throws Throwable { + try { + nativeDestructor(mNativePicture); + } finally { + super.finalize(); + } } - + /** * To record a picture, call beginRecording() and then draw into the Canvas * that is returned. Nothing we appear on screen, but all of the draw @@ -67,7 +78,7 @@ public class Picture { mRecordingCanvas = new RecordingCanvas(this, ni); return mRecordingCanvas; } - + /** * Call endRecording when the picture is built. After this call, the picture * may be drawn, but the canvas that was returned by beginRecording must not @@ -85,29 +96,36 @@ public class Picture { * Get the width of the picture as passed to beginRecording. This * does not reflect (per se) the content of the picture. */ - public native int getWidth(); + public int getWidth() { + return nativeGetWidth(mNativePicture); + } /** * Get the height of the picture as passed to beginRecording. This * does not reflect (per se) the content of the picture. */ - public native int getHeight(); - + public int getHeight() { + return nativeGetHeight(mNativePicture); + } + /** - * Draw this picture on the canvas. The picture may have the side effect - * of changing the matrix and clip of the canvas. - * + * Draw this picture on the canvas. + * <p> + * Prior to {@link android.os.Build.VERSION_CODES#L}, this call could + * have the side effect of changing the matrix and clip of the canvas + * if this picture had imbalanced saves/restores. + * * <p> * <strong>Note:</strong> This forces the picture to internally call * {@link Picture#endRecording()} in order to prepare for playback. * - * @param canvas The picture is drawn to this canvas + * @param canvas The picture is drawn to this canvas */ public void draw(Canvas canvas) { if (mRecordingCanvas != null) { endRecording(); } - nativeDraw(canvas.getNativeCanvas(), mNativePicture); + nativeDraw(canvas.getNativeCanvasWrapper(), mNativePicture); } /** @@ -119,7 +137,7 @@ public class Picture { * <p> * <strong>Note:</strong> a picture created from an input stream cannot be * replayed on a hardware accelerated canvas. - * + * * @see #writeToStream(java.io.OutputStream) * @deprecated The recommended alternative is to not use writeToStream and * instead draw the picture into a Bitmap from which you can persist it as @@ -127,7 +145,7 @@ public class Picture { */ @Deprecated public static Picture createFromStream(InputStream stream) { - return new Picture(nativeCreateFromStream(stream, new byte[WORKING_STREAM_STORAGE]), true); + return new Picture(nativeCreateFromStream(stream, new byte[WORKING_STREAM_STORAGE])); } /** @@ -156,38 +174,18 @@ public class Picture { } } - protected void finalize() throws Throwable { - try { - nativeDestructor(mNativePicture); - } finally { - super.finalize(); - } - } - - final long ni() { - return mNativePicture; - } - - private Picture(long nativePicture, boolean fromStream) { - if (nativePicture == 0) { - throw new RuntimeException(); - } - mNativePicture = nativePicture; - createdFromStream = fromStream; - } - // return empty picture if src is 0, or a copy of the native src private static native long nativeConstructor(long nativeSrcOr0); - private static native long nativeCreateFromStream(InputStream stream, - byte[] storage); - private static native long nativeBeginRecording(long nativeCanvas, - int w, int h); + private static native long nativeCreateFromStream(InputStream stream, byte[] storage); + private static native int nativeGetWidth(long nativePicture); + private static native int nativeGetHeight(long nativePicture); + private static native long nativeBeginRecording(long nativeCanvas, int w, int h); private static native void nativeEndRecording(long nativeCanvas); private static native void nativeDraw(long nativeCanvas, long nativePicture); private static native boolean nativeWriteToStream(long nativePicture, OutputStream stream, byte[] storage); private static native void nativeDestructor(long nativePicture); - + private static class RecordingCanvas extends Canvas { private final Picture mPicture; @@ -195,21 +193,18 @@ public class Picture { super(nativeCanvas); mPicture = pict; } - + @Override public void setBitmap(Bitmap bitmap) { - throw new RuntimeException( - "Cannot call setBitmap on a picture canvas"); + throw new RuntimeException("Cannot call setBitmap on a picture canvas"); } @Override public void drawPicture(Picture picture) { if (mPicture == picture) { - throw new RuntimeException( - "Cannot draw a picture into its recording canvas"); + throw new RuntimeException("Cannot draw a picture into its recording canvas"); } super.drawPicture(picture); } } } - diff --git a/graphics/java/android/graphics/drawable/Animatable.java b/graphics/java/android/graphics/drawable/Animatable.java index 9dc62c3..4edfad4 100644 --- a/graphics/java/android/graphics/drawable/Animatable.java +++ b/graphics/java/android/graphics/drawable/Animatable.java @@ -32,7 +32,7 @@ public interface Animatable { /** * Indicates whether the animation is running. - * + * * @return True if the animation is running, false otherwise. */ boolean isRunning(); diff --git a/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java b/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java index e8024f7..0fd4423 100644 --- a/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java +++ b/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java @@ -19,6 +19,8 @@ package android.graphics.drawable; import android.graphics.Canvas; import android.graphics.Rect; import android.graphics.ColorFilter; +import android.graphics.PorterDuff.Mode; +import android.content.res.ColorStateList; import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.Resources.Theme; @@ -88,6 +90,7 @@ public class AnimatedRotateDrawable extends Drawable implements Drawable.Callbac canvas.restoreToCount(saveCount); } + @Override public void start() { if (!mRunning) { mRunning = true; @@ -95,11 +98,13 @@ public class AnimatedRotateDrawable extends Drawable implements Drawable.Callbac } } + @Override public void stop() { mRunning = false; unscheduleSelf(this); } + @Override public boolean isRunning() { return mRunning; } @@ -108,10 +113,11 @@ public class AnimatedRotateDrawable extends Drawable implements Drawable.Callbac unscheduleSelf(this); scheduleSelf(this, SystemClock.uptimeMillis() + mState.mFrameDuration); } - + + @Override public void run() { // TODO: This should be computed in draw(Canvas), based on the amount - // of time since the last frame drawn + // of time since the last frame drawn mCurrentDegrees += mIncrement; if (mCurrentDegrees > (360.0f - mIncrement)) { mCurrentDegrees = 0.0f; @@ -119,7 +125,7 @@ public class AnimatedRotateDrawable extends Drawable implements Drawable.Callbac invalidateSelf(); nextFrame(); } - + @Override public boolean setVisible(boolean visible, boolean restart) { mState.mDrawable.setVisible(visible, restart); @@ -133,8 +139,8 @@ public class AnimatedRotateDrawable extends Drawable implements Drawable.Callbac unscheduleSelf(this); } return changed; - } - + } + /** * Returns the drawable rotated by this RotateDrawable. */ @@ -148,7 +154,7 @@ public class AnimatedRotateDrawable extends Drawable implements Drawable.Callbac | mState.mChangingConfigurations | mState.mDrawable.getChangingConfigurations(); } - + @Override public void setAlpha(int alpha) { mState.mDrawable.setAlpha(alpha); @@ -165,10 +171,16 @@ public class AnimatedRotateDrawable extends Drawable implements Drawable.Callbac } @Override + public void setTint(ColorStateList tint, Mode tintMode) { + mState.mDrawable.setTint(tint, tintMode); + } + + @Override public int getOpacity() { return mState.mDrawable.getOpacity(); } + @Override public void invalidateDrawable(Drawable who) { final Callback callback = getCallback(); if (callback != null) { @@ -176,6 +188,7 @@ public class AnimatedRotateDrawable extends Drawable implements Drawable.Callbac } } + @Override public void scheduleDrawable(Drawable who, Runnable what, long when) { final Callback callback = getCallback(); if (callback != null) { @@ -183,6 +196,7 @@ public class AnimatedRotateDrawable extends Drawable implements Drawable.Callbac } } + @Override public void unscheduleDrawable(Drawable who, Runnable what) { final Callback callback = getCallback(); if (callback != null) { @@ -194,7 +208,7 @@ public class AnimatedRotateDrawable extends Drawable implements Drawable.Callbac public boolean getPadding(Rect padding) { return mState.mDrawable.getPadding(padding); } - + @Override public boolean isStateful() { return mState.mDrawable.isStateful(); @@ -206,6 +220,16 @@ public class AnimatedRotateDrawable extends Drawable implements Drawable.Callbac } @Override + protected boolean onLevelChange(int level) { + return mState.mDrawable.setLevel(level); + } + + @Override + protected boolean onStateChange(int[] state) { + return mState.mDrawable.setState(state); + } + + @Override public int getIntrinsicWidth() { return mState.mDrawable.getIntrinsicWidth(); } @@ -231,11 +255,11 @@ public class AnimatedRotateDrawable extends Drawable implements Drawable.Callbac final TypedArray a = r.obtainAttributes(attrs, R.styleable.AnimatedRotateDrawable); super.inflateWithAttributes(r, parser, a, R.styleable.AnimatedRotateDrawable_visible); - + 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(); - + 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(); @@ -250,7 +274,7 @@ public class AnimatedRotateDrawable extends Drawable implements Drawable.Callbac } a.recycle(); - + int outerDepth = parser.getDepth(); int type; while ((type = parser.next()) != XmlPullParser.END_DOCUMENT && @@ -306,7 +330,7 @@ public class AnimatedRotateDrawable extends Drawable implements Drawable.Callbac Drawable mDrawable; int mChangingConfigurations; - + boolean mPivotXRel; float mPivotX; boolean mPivotYRel; @@ -315,7 +339,7 @@ public class AnimatedRotateDrawable extends Drawable implements Drawable.Callbac int mFramesCount; private boolean mCanConstantState; - private boolean mCheckedConstantState; + private boolean mCheckedConstantState; public AnimatedRotateState(AnimatedRotateState source, AnimatedRotateDrawable owner, Resources res) { @@ -341,12 +365,12 @@ public class AnimatedRotateDrawable extends Drawable implements Drawable.Callbac public Drawable newDrawable() { return new AnimatedRotateDrawable(this, null); } - + @Override public Drawable newDrawable(Resources res) { return new AnimatedRotateDrawable(this, res); } - + @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 a5a074c..e1dec9d 100644 --- a/graphics/java/android/graphics/drawable/AnimatedStateListDrawable.java +++ b/graphics/java/android/graphics/drawable/AnimatedStateListDrawable.java @@ -537,4 +537,3 @@ public class AnimatedStateListDrawable extends StateListDrawable { } } } - diff --git a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java new file mode 100644 index 0000000..c787fb0 --- /dev/null +++ b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java @@ -0,0 +1,331 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package android.graphics.drawable; + +import android.animation.Animator; +import android.animation.AnimatorInflater; +import android.content.res.Resources; +import android.content.res.Resources.Theme; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.ColorFilter; +import android.graphics.Rect; +import android.util.AttributeSet; +import android.util.Log; + +import com.android.internal.R; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.util.ArrayList; + +/** + * This class uses {@link android.animation.ObjectAnimator} and + * {@link android.animation.AnimatorSet} to animate the properties of a + * {@link android.graphics.drawable.VectorDrawable} to create an animated drawable. + * <p> + * AnimatedVectorDrawable are normally defined as 3 separate XML files. + * </p> + * <p> + * First is the XML file for {@link android.graphics.drawable.VectorDrawable}. + * Note that we allow the animation happen on the group's attributes and path's + * attributes, which requires they are uniquely named in this xml file. Groups + * and paths without animations do not need names. + * </p> + * <li>Here is a simple VectorDrawable in this vectordrawable.xml file. + * <pre> + * <vector xmlns:android="http://schemas.android.com/apk/res/android" > + * <size + * android:height="64dp" + * android:width="64dp" /> + * <viewport + * android:viewportHeight="600" + * android:viewportWidth="600" /> + * <group + * android:name="rotationGroup" + * android:pivotX="300.0" + * android:pivotY="300.0" + * android:rotation="45.0" > + * <path + * android:name="v" + * android:fill="#000000" + * android:pathData="M300,70 l 0,-70 70,70 0,0 -70,70z" /> + * </group> + * </vector> + * </pre></li> + * <p> + * Second is the AnimatedVectorDrawable's xml file, which defines the target + * VectorDrawable, the target paths and groups to animate, the properties of the + * path and group to animate and the animations defined as the ObjectAnimators + * or AnimatorSets. + * </p> + * <li>Here is a simple AnimatedVectorDrawable defined in this avd.xml file. + * Note how we use the names to refer to the groups and paths in the vectordrawable.xml. + * <pre> + * <animated-vector xmlns:android="http://schemas.android.com/apk/res/android" + * android:drawable="@drawable/vectordrawable" > + * <target + * android:name="rotationGroup" + * android:animation="@anim/rotation" /> + * <target + * android:name="v" + * android:animation="@anim/path_morph" /> + * </animated-vector> + * </pre></li> + * <p> + * Last is the Animator xml file, which is the same as a normal ObjectAnimator + * or AnimatorSet. + * To complete this example, here are the 2 animator files used in avd.xml: + * rotation.xml and path_morph.xml. + * </p> + * <li>Here is the rotation.xml, which will rotate the target group for 360 degrees. + * <pre> + * <objectAnimator + * android:duration="6000" + * android:propertyName="rotation" + * android:valueFrom="0" + * android:valueTo="360" /> + * </pre></li> + * <li>Here is the path_morph.xml, which will morph the path from one shape to + * the other. Note that the paths must be compatible for morphing. + * In more details, the paths should have exact same length of commands , and + * exact same length of parameters for each commands. + * Note that the path string are better stored in strings.xml for reusing. + * <pre> + * <set xmlns:android="http://schemas.android.com/apk/res/android"> + * <objectAnimator + * android:duration="3000" + * android:propertyName="pathData" + * android:valueFrom="M300,70 l 0,-70 70,70 0,0 -70,70z" + * android:valueTo="M300,70 l 0,-70 70,0 0,140 -70,0 z" + * android:valueType="pathType"/> + * </set> + * </pre></li> + * + * @attr ref android.R.styleable#AnimatedVectorDrawable_drawable + * @attr ref android.R.styleable#AnimatedVectorDrawableTarget_name + * @attr ref android.R.styleable#AnimatedVectorDrawableTarget_animation + */ +public class AnimatedVectorDrawable extends Drawable implements Animatable { + private static final String LOGTAG = AnimatedVectorDrawable.class.getSimpleName(); + + private static final String ANIMATED_VECTOR = "animated-vector"; + private static final String TARGET = "target"; + + private static final boolean DBG_ANIMATION_VECTOR_DRAWABLE = false; + + private final AnimatedVectorDrawableState mAnimatedVectorState; + + + public AnimatedVectorDrawable() { + mAnimatedVectorState = new AnimatedVectorDrawableState( + new AnimatedVectorDrawableState(null)); + } + + private AnimatedVectorDrawable(AnimatedVectorDrawableState state, Resources res, + Theme theme) { + // TODO: Correctly handle the constant state for AVD. + mAnimatedVectorState = new AnimatedVectorDrawableState(state); + if (theme != null && canApplyTheme()) { + applyTheme(theme); + } + } + + @Override + public ConstantState getConstantState() { + return null; + } + + @Override + public void draw(Canvas canvas) { + mAnimatedVectorState.mVectorDrawable.draw(canvas); + if (isRunning()) { + invalidateSelf(); + } + } + + @Override + protected void onBoundsChange(Rect bounds) { + mAnimatedVectorState.mVectorDrawable.setBounds(bounds); + } + + @Override + public int getAlpha() { + return mAnimatedVectorState.mVectorDrawable.getAlpha(); + } + + @Override + public void setAlpha(int alpha) { + mAnimatedVectorState.mVectorDrawable.setAlpha(alpha); + } + + @Override + public void setColorFilter(ColorFilter colorFilter) { + mAnimatedVectorState.mVectorDrawable.setColorFilter(colorFilter); + } + + @Override + public int getOpacity() { + return mAnimatedVectorState.mVectorDrawable.getOpacity(); + } + + @Override + public int getIntrinsicWidth() { + return mAnimatedVectorState.mVectorDrawable.getIntrinsicWidth(); + } + + @Override + public int getIntrinsicHeight() { + return mAnimatedVectorState.mVectorDrawable.getIntrinsicHeight(); + } + + @Override + public void inflate(Resources res, XmlPullParser parser, AttributeSet attrs, Theme theme) + throws XmlPullParserException, IOException { + + int eventType = parser.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + final String tagName = parser.getName(); + if (ANIMATED_VECTOR.equals(tagName)) { + final TypedArray a = obtainAttributes(res, theme, attrs, + R.styleable.AnimatedVectorDrawable); + int drawableRes = a.getResourceId( + R.styleable.AnimatedVectorDrawable_drawable, 0); + if (drawableRes != 0) { + mAnimatedVectorState.mVectorDrawable = (VectorDrawable) res.getDrawable( + drawableRes); + } + a.recycle(); + } else if (TARGET.equals(tagName)) { + final TypedArray a = obtainAttributes(res, theme, attrs, + R.styleable.AnimatedVectorDrawableTarget); + final String target = a.getString( + R.styleable.AnimatedVectorDrawableTarget_name); + + int id = a.getResourceId( + R.styleable.AnimatedVectorDrawableTarget_animation, 0); + if (id != 0) { + Animator objectAnimator = AnimatorInflater.loadAnimator(res, theme, id); + setupAnimatorsForTarget(target, objectAnimator); + } + a.recycle(); + } + } + + eventType = parser.next(); + } + } + + @Override + public boolean canApplyTheme() { + return super.canApplyTheme() || mAnimatedVectorState != null + && mAnimatedVectorState.canApplyTheme(); + } + + @Override + public void applyTheme(Theme t) { + super.applyTheme(t); + + final VectorDrawable vectorDrawable = mAnimatedVectorState.mVectorDrawable; + if (vectorDrawable != null && vectorDrawable.canApplyTheme()) { + vectorDrawable.applyTheme(t); + } + } + + private static class AnimatedVectorDrawableState extends ConstantState { + int mChangingConfigurations; + VectorDrawable mVectorDrawable; + ArrayList<Animator> mAnimators; + + public AnimatedVectorDrawableState(AnimatedVectorDrawableState copy) { + if (copy != null) { + mChangingConfigurations = copy.mChangingConfigurations; + // TODO: Make sure the constant state are handled correctly. + mVectorDrawable = new VectorDrawable(); + mAnimators = new ArrayList<Animator>(); + } + } + + @Override + public Drawable newDrawable() { + return new AnimatedVectorDrawable(this, null, null); + } + + @Override + public Drawable newDrawable(Resources res) { + return new AnimatedVectorDrawable(this, res, null); + } + + @Override + public Drawable newDrawable(Resources res, Theme theme) { + return new AnimatedVectorDrawable(this, res, theme); + } + + @Override + public int getChangingConfigurations() { + return mChangingConfigurations; + } + } + + private void setupAnimatorsForTarget(String name, Animator animator) { + Object target = mAnimatedVectorState.mVectorDrawable.getTargetByName(name); + animator.setTarget(target); + mAnimatedVectorState.mAnimators.add(animator); + if (DBG_ANIMATION_VECTOR_DRAWABLE) { + Log.v(LOGTAG, "add animator for target " + name + " " + animator); + } + } + + @Override + public boolean isRunning() { + 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.isRunning()) { + return true; + } + } + return false; + } + + @Override + public void start() { + 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.isPaused()) { + animator.resume(); + } else if (!animator.isRunning()) { + animator.start(); + } + } + invalidateSelf(); + } + + @Override + public void stop() { + final ArrayList<Animator> animators = mAnimatedVectorState.mAnimators; + final int size = animators.size(); + for (int i = 0; i < size; i++) { + final Animator animator = animators.get(i); + animator.pause(); + } + } +} diff --git a/graphics/java/android/graphics/drawable/AnimationDrawable.java b/graphics/java/android/graphics/drawable/AnimationDrawable.java index 16548d0..0740761 100644 --- a/graphics/java/android/graphics/drawable/AnimationDrawable.java +++ b/graphics/java/android/graphics/drawable/AnimationDrawable.java @@ -28,7 +28,6 @@ import android.os.SystemClock; import android.util.AttributeSet; /** - * * An object used to create frame-by-frame animations, defined by a series of Drawable objects, * which can be used as a View object's background. * <p> @@ -91,10 +90,10 @@ public class AnimationDrawable extends DrawableContainer implements Runnable, An @Override public boolean setVisible(boolean visible, boolean restart) { - boolean changed = super.setVisible(visible, restart); + final boolean changed = super.setVisible(visible, restart); if (visible) { if (changed || restart) { - setFrame(0, true, restart || mCurFrame >= 0); + setFrame(0, true, mAnimating); } } else { unscheduleSelf(this); @@ -114,6 +113,7 @@ public class AnimationDrawable extends DrawableContainer implements Runnable, An * @see #isRunning() * @see #stop() */ + @Override public void start() { if (!isRunning()) { run(); @@ -127,6 +127,7 @@ public class AnimationDrawable extends DrawableContainer implements Runnable, An * @see #isRunning() * @see #start() */ + @Override public void stop() { if (isRunning()) { unscheduleSelf(this); @@ -138,6 +139,7 @@ public class AnimationDrawable extends DrawableContainer implements Runnable, An * * @return true if the animation is running, false otherwise */ + @Override public boolean isRunning() { return mAnimating; } @@ -148,6 +150,7 @@ public class AnimationDrawable extends DrawableContainer implements Runnable, An * * @see #start() */ + @Override public void run() { nextFrame(false); } @@ -165,41 +168,41 @@ public class AnimationDrawable extends DrawableContainer implements Runnable, An public int getNumberOfFrames() { return mAnimationState.getChildCount(); } - + /** * @return The Drawable at the specified frame index */ public Drawable getFrame(int index) { return mAnimationState.getChild(index); } - + /** - * @return The duration in milliseconds of the frame at the + * @return The duration in milliseconds of the frame at the * specified index */ public int getDuration(int i) { return mAnimationState.mDurations[i]; } - + /** * @return True of the animation will play once, false otherwise */ public boolean isOneShot() { return mAnimationState.mOneShot; } - + /** * Sets whether the animation should play once or repeat. - * + * * @param oneShot Pass true if the animation should only play once */ public void setOneShot(boolean oneShot) { mAnimationState.mOneShot = oneShot; } - + /** * Add a frame to the animation - * + * * @param frame The frame to add * @param duration How long in milliseconds the frame should appear */ @@ -209,7 +212,7 @@ public class AnimationDrawable extends DrawableContainer implements Runnable, An setFrame(0, true, false); } } - + private void nextFrame(boolean unschedule) { int next = mCurFrame+1; final int N = mAnimationState.getChildCount(); @@ -239,21 +242,21 @@ public class AnimationDrawable extends DrawableContainer implements Runnable, An @Override public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) throws XmlPullParserException, IOException { - + TypedArray a = r.obtainAttributes(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); - + a.recycle(); - + int type; final int innerDepth = parser.getDepth()+1; @@ -267,7 +270,7 @@ public class AnimationDrawable extends DrawableContainer implements Runnable, An if (depth > innerDepth || !parser.getName().equals("item")) { continue; } - + a = r.obtainAttributes(attrs, com.android.internal.R.styleable.AnimationDrawableItem); int duration = a.getInt( com.android.internal.R.styleable.AnimationDrawableItem_duration, -1); @@ -278,9 +281,9 @@ public class AnimationDrawable extends DrawableContainer implements Runnable, An } int drawableRes = a.getResourceId( com.android.internal.R.styleable.AnimationDrawableItem_drawable, 0); - + a.recycle(); - + Drawable dr; if (drawableRes != 0) { dr = r.getDrawable(drawableRes); @@ -295,7 +298,7 @@ public class AnimationDrawable extends DrawableContainer implements Runnable, An } dr = Drawable.createFromXmlInner(r, parser, attrs, theme); } - + mAnimationState.addFrame(dr, duration); if (dr != null) { dr.setCallback(this); @@ -342,7 +345,7 @@ public class AnimationDrawable extends DrawableContainer implements Runnable, An } public void addFrame(Drawable dr, int dur) { - // Do not combine the following. The array index must be evaluated before + // Do not combine the following. The array index must be evaluated before // the array is accessed because super.addChild(dr) has a side effect on mDurations. int pos = super.addChild(dr); mDurations[pos] = dur; diff --git a/graphics/java/android/graphics/drawable/BitmapDrawable.java b/graphics/java/android/graphics/drawable/BitmapDrawable.java index ef6c085..e080375 100644 --- a/graphics/java/android/graphics/drawable/BitmapDrawable.java +++ b/graphics/java/android/graphics/drawable/BitmapDrawable.java @@ -31,6 +31,7 @@ import android.graphics.Paint; import android.graphics.PixelFormat; import android.graphics.PorterDuff; import android.graphics.PorterDuff.Mode; +import android.graphics.drawable.ColorDrawable.ColorState; import android.graphics.PorterDuffColorFilter; import android.graphics.Rect; import android.graphics.Shader; @@ -618,9 +619,11 @@ public class BitmapDrawable extends Drawable { @Override public void setTint(ColorStateList tint, PorterDuff.Mode tintMode) { - mBitmapState.mTint = tint; - mBitmapState.mTintMode = tintMode; - computeTintFilter(); + final BitmapState state = mBitmapState; + state.mTint = tint; + state.mTintMode = tintMode; + + mTintFilter = updateTintFilter(mTintFilter, tint, tintMode); invalidateSelf(); } @@ -638,21 +641,6 @@ public class BitmapDrawable extends Drawable { return mBitmapState.mTintMode; } - private void computeTintFilter() { - final BitmapState state = mBitmapState; - if (state.mTint != null && state.mTintMode != null) { - final int color = state.mTint.getColorForState(getState(), 0); - if (mTintFilter != null) { - mTintFilter.setColor(color); - mTintFilter.setMode(state.mTintMode); - } else { - mTintFilter = new PorterDuffColorFilter(color, state.mTintMode); - } - } else { - mTintFilter = null; - } - } - /** * @hide Candidate for future API inclusion */ @@ -679,17 +667,11 @@ public class BitmapDrawable extends Drawable { @Override protected boolean onStateChange(int[] stateSet) { - final ColorStateList tint = mBitmapState.mTint; - if (tint != null) { - final int newColor = tint.getColorForState(stateSet, 0); - final int oldColor = mTintFilter.getColor(); - if (oldColor != newColor) { - mTintFilter.setColor(newColor); - invalidateSelf(); - return true; - } + final BitmapState state = mBitmapState; + if (state.mTint != null && state.mTintMode != null) { + mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode); + return true; } - return false; } @@ -775,7 +757,18 @@ public class BitmapDrawable extends Drawable { final int tileMode = a.getInt(R.styleable.BitmapDrawable_tileMode, TILE_MODE_UNDEFINED); if (tileMode != TILE_MODE_UNDEFINED) { - setTileModeInternal(tileMode); + final Shader.TileMode mode = parseTileMode(tileMode); + setTileModeXY(mode, mode); + } + + final int tileModeX = a.getInt(R.styleable.BitmapDrawable_tileModeX, TILE_MODE_UNDEFINED); + if (tileModeX != TILE_MODE_UNDEFINED) { + setTileModeX(parseTileMode(tileModeX)); + } + + final int tileModeY = a.getInt(R.styleable.BitmapDrawable_tileModeY, TILE_MODE_UNDEFINED); + if (tileModeY != TILE_MODE_UNDEFINED) { + setTileModeY(parseTileMode(tileModeY)); } // Update local properties. @@ -801,20 +794,16 @@ public class BitmapDrawable extends Drawable { } } - private void setTileModeInternal(final int tileMode) { + private static Shader.TileMode parseTileMode(int tileMode) { switch (tileMode) { - case TILE_MODE_DISABLED: - setTileModeXY(null, null); - break; case TILE_MODE_CLAMP: - setTileModeXY(Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); - break; + return Shader.TileMode.CLAMP; case TILE_MODE_REPEAT: - setTileModeXY(Shader.TileMode.REPEAT, Shader.TileMode.REPEAT); - break; + return Shader.TileMode.REPEAT; case TILE_MODE_MIRROR: - setTileModeXY(Shader.TileMode.MIRROR, Shader.TileMode.MIRROR); - break; + return Shader.TileMode.MIRROR; + default: + return null; } } @@ -937,7 +926,9 @@ public class BitmapDrawable extends Drawable { } /** - * Initializes local dynamic properties from state. + * Initializes local dynamic properties from state. This should be called + * after significant state changes, e.g. from the One True Constructor and + * after inflating or applying a theme. */ private void initializeWithState(BitmapState state, Resources res) { if (res != null) { @@ -946,7 +937,7 @@ public class BitmapDrawable extends Drawable { mTargetDensity = state.mTargetDensity; } - computeTintFilter(); + mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode); computeBitmapSize(); } } diff --git a/graphics/java/android/graphics/drawable/ClipDrawable.java b/graphics/java/android/graphics/drawable/ClipDrawable.java index 3ac9972..174de3a 100644 --- a/graphics/java/android/graphics/drawable/ClipDrawable.java +++ b/graphics/java/android/graphics/drawable/ClipDrawable.java @@ -19,10 +19,12 @@ package android.graphics.drawable; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; +import android.content.res.ColorStateList; import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.Resources.Theme; import android.graphics.*; +import android.graphics.PorterDuff.Mode; import android.view.Gravity; import android.util.AttributeSet; @@ -52,7 +54,7 @@ public class ClipDrawable extends Drawable implements Drawable.Callback { public static final int HORIZONTAL = 1; public static final int VERTICAL = 2; - + ClipDrawable() { this(null, null); } @@ -111,6 +113,7 @@ public class ClipDrawable extends Drawable implements Drawable.Callback { // overrides from Drawable.Callback + @Override public void invalidateDrawable(Drawable who) { final Callback callback = getCallback(); if (callback != null) { @@ -118,6 +121,7 @@ public class ClipDrawable extends Drawable implements Drawable.Callback { } } + @Override public void scheduleDrawable(Drawable who, Runnable what, long when) { final Callback callback = getCallback(); if (callback != null) { @@ -125,6 +129,7 @@ public class ClipDrawable extends Drawable implements Drawable.Callback { } } + @Override public void unscheduleDrawable(Drawable who, Runnable what) { final Callback callback = getCallback(); if (callback != null) { @@ -169,6 +174,11 @@ public class ClipDrawable extends Drawable implements Drawable.Callback { } @Override + public void setTint(ColorStateList tint, Mode tintMode) { + mClipState.mDrawable.setTint(tint, tintMode); + } + + @Override public int getOpacity() { return mClipState.mDrawable.getOpacity(); } @@ -197,7 +207,7 @@ public class ClipDrawable extends Drawable implements Drawable.Callback { @Override public void draw(Canvas canvas) { - + if (mClipState.mDrawable.getLevel() == 0) { return; } diff --git a/graphics/java/android/graphics/drawable/ColorDrawable.java b/graphics/java/android/graphics/drawable/ColorDrawable.java index df5ca33..3716182 100644 --- a/graphics/java/android/graphics/drawable/ColorDrawable.java +++ b/graphics/java/android/graphics/drawable/ColorDrawable.java @@ -17,6 +17,8 @@ package android.graphics.drawable; import android.graphics.*; +import android.graphics.PorterDuff.Mode; +import android.content.res.ColorStateList; import android.content.res.Resources; import android.content.res.Resources.Theme; import android.content.res.TypedArray; @@ -39,9 +41,13 @@ import java.io.IOException; * @attr ref android.R.styleable#ColorDrawable_color */ public class ColorDrawable extends Drawable { + private final Paint mPaint = new Paint(); + @ViewDebug.ExportedProperty(deepExport = true, prefix = "state_") private ColorState mColorState; - private final Paint mPaint = new Paint(); + private ColorStateList mTint; + private PorterDuffColorFilter mTintFilter; + private boolean mMutated; /** @@ -84,9 +90,17 @@ public class ColorDrawable extends Drawable { @Override public void draw(Canvas canvas) { - if ((mColorState.mUseColor >>> 24) != 0) { + final ColorFilter colorFilter = mPaint.getColorFilter(); + if ((mColorState.mUseColor >>> 24) != 0 || colorFilter != null || mTintFilter != null) { + if (colorFilter == null) { + mPaint.setColorFilter(mTintFilter); + } + mPaint.setColor(mColorState.mUseColor); canvas.drawRect(getBounds(), mPaint); + + // Restore original color filter. + mPaint.setColorFilter(colorFilter); } } @@ -141,16 +155,51 @@ public class ColorDrawable extends Drawable { } /** - * Setting a color filter on a ColorDrawable has no effect. + * Sets the color filter applied to this color. + * <p> + * Only supported on version {@link android.os.Build.VERSION_CODES#L} and + * above. Calling this method has no effect on earlier versions. * - * @param colorFilter Ignore. + * @see android.graphics.drawable.Drawable#setColorFilter(ColorFilter) */ @Override public void setColorFilter(ColorFilter colorFilter) { + mPaint.setColorFilter(colorFilter); + } + + @Override + public void setTint(ColorStateList tint, Mode tintMode) { + final ColorState state = mColorState; + if (state.mTint != tint || state.mTintMode != tintMode) { + state.mTint = tint; + state.mTintMode = tintMode; + + mTintFilter = updateTintFilter(mTintFilter, tint, tintMode); + invalidateSelf(); + } + } + + @Override + protected boolean onStateChange(int[] stateSet) { + final ColorState state = mColorState; + if (state.mTint != null && state.mTintMode != null) { + mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode); + return true; + } + return false; + } + + @Override + public boolean isStateful() { + return mTint != null && mTint.isStateful(); } @Override public int getOpacity() { + if (mTintFilter != null || mPaint.getColorFilter() != null) { + return PixelFormat.TRANSLUCENT; + } + switch (mColorState.mUseColor >>> 24) { case 255: return PixelFormat.OPAQUE; @@ -165,8 +214,7 @@ public class ColorDrawable extends Drawable { throws XmlPullParserException, IOException { super.inflate(r, parser, attrs, theme); - final TypedArray a = obtainAttributes( - r, theme, attrs, R.styleable.ColorDrawable); + final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.ColorDrawable); inflateStateFromTypedArray(a); a.recycle(); } @@ -225,21 +273,25 @@ public class ColorDrawable extends Drawable { } final static class ColorState extends ConstantState { + int[] mThemeAttrs; int mBaseColor; // base color, independent of setAlpha() @ViewDebug.ExportedProperty int mUseColor; // basecolor modulated by setAlpha() int mChangingConfigurations; - int[] mThemeAttrs; + ColorStateList mTint; + Mode mTintMode; ColorState() { // Empty constructor. } ColorState(ColorState state) { + mThemeAttrs = state.mThemeAttrs; mBaseColor = state.mBaseColor; mUseColor = state.mUseColor; mChangingConfigurations = state.mChangingConfigurations; - mThemeAttrs = state.mThemeAttrs; + mTint = state.mTint; + mTintMode = state.mTintMode; } @Override @@ -276,6 +328,6 @@ public class ColorDrawable extends Drawable { mColorState = state; } - // No local properties to initialize. + 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 18e8e52..40b55a7 100644 --- a/graphics/java/android/graphics/drawable/Drawable.java +++ b/graphics/java/android/graphics/drawable/Drawable.java @@ -17,13 +17,6 @@ package android.graphics.drawable; import android.annotation.NonNull; -import android.graphics.Insets; -import android.graphics.Xfermode; -import android.os.Trace; - -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; - import android.content.res.ColorStateList; import android.content.res.Resources; import android.content.res.Resources.Theme; @@ -31,15 +24,19 @@ import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; +import android.graphics.Color; import android.graphics.ColorFilter; +import android.graphics.Insets; import android.graphics.NinePatch; import android.graphics.Outline; import android.graphics.PixelFormat; import android.graphics.PorterDuff; +import android.graphics.PorterDuff.Mode; import android.graphics.PorterDuffColorFilter; import android.graphics.Rect; import android.graphics.Region; -import android.graphics.PorterDuff.Mode; +import android.graphics.Xfermode; +import android.os.Trace; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.util.StateSet; @@ -47,6 +44,9 @@ import android.util.TypedValue; import android.util.Xml; import android.view.View; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + import java.io.IOException; import java.io.InputStream; import java.lang.ref.WeakReference; @@ -209,7 +209,7 @@ public abstract class Drawable { * stored bounds of this drawable. * * @see #copyBounds() - * @see #copyBounds(android.graphics.Rect) + * @see #copyBounds(android.graphics.Rect) */ public final Rect getBounds() { if (mBounds == ZERO_BOUNDS_RECT) { @@ -328,8 +328,8 @@ public abstract class Drawable { * that want to support animated drawables. * * @param cb The client's Callback implementation. - * - * @see #getCallback() + * + * @see #getCallback() */ public final void setCallback(Callback cb) { mCallback = new WeakReference<Callback>(cb); @@ -338,10 +338,10 @@ public abstract class Drawable { /** * Return the current {@link Callback} implementation attached to this * Drawable. - * + * * @return A {@link Callback} instance or null if no callback was set. - * - * @see #setCallback(android.graphics.drawable.Drawable.Callback) + * + * @see #setCallback(android.graphics.drawable.Drawable.Callback) */ public Callback getCallback() { if (mCallback != null) { @@ -349,15 +349,15 @@ public abstract class Drawable { } return null; } - + /** * Use the current {@link Callback} implementation to have this Drawable * redrawn. Does nothing if there is no Callback attached to the * Drawable. * * @see Callback#invalidateDrawable - * @see #getCallback() - * @see #setCallback(android.graphics.drawable.Drawable.Callback) + * @see #getCallback() + * @see #setCallback(android.graphics.drawable.Drawable.Callback) */ public void invalidateSelf() { final Callback callback = getCallback(); @@ -931,7 +931,7 @@ public abstract class Drawable { Rects only to drop them on the floor. */ Rect pad = new Rect(); - + // Special stuff for compatibility mode: if the target density is not // the same as the display density, but the resource -is- the same as // the display density, then don't scale it down to the target density. @@ -1040,6 +1040,8 @@ public abstract class Drawable { 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")) { @@ -1047,7 +1049,7 @@ public abstract class Drawable { } else if (name.equals("rotate")) { drawable = new RotateDrawable(); } else if (name.equals("animated-rotate")) { - drawable = new AnimatedRotateDrawable(); + drawable = new AnimatedRotateDrawable(); } else if (name.equals("animation-list")) { drawable = new AnimationDrawable(); } else if (name.equals("inset")) { @@ -1224,6 +1226,26 @@ public abstract class Drawable { } /** + * Ensures the tint filter is consistent with the current tint color and + * mode. + */ + PorterDuffColorFilter updateTintFilter(PorterDuffColorFilter tintFilter, ColorStateList tint, + PorterDuff.Mode tintMode) { + if (tint == null || tintMode == null) { + return null; + } + + final int color = tint.getColorForState(getState(), Color.TRANSPARENT); + if (tintFilter == null) { + return new PorterDuffColorFilter(color, tintMode); + } + + tintFilter.setColor(color); + tintFilter.setMode(tintMode); + return tintFilter; + } + + /** * Obtains styled attributes from the theme, if available, or unstyled * resources if the theme is null. */ @@ -1238,8 +1260,10 @@ public abstract class Drawable { /** * Parses a {@link android.graphics.PorterDuff.Mode} from a tintMode * attribute's enum value. + * + * @hide */ - static PorterDuff.Mode parseTintMode(int value, Mode defaultMode) { + public static PorterDuff.Mode parseTintMode(int value, Mode defaultMode) { switch (value) { case 3: return Mode.SRC_OVER; case 5: return Mode.SRC_IN; diff --git a/graphics/java/android/graphics/drawable/DrawableContainer.java b/graphics/java/android/graphics/drawable/DrawableContainer.java index 2aef39f..38b8aaf 100644 --- a/graphics/java/android/graphics/drawable/DrawableContainer.java +++ b/graphics/java/android/graphics/drawable/DrawableContainer.java @@ -16,6 +16,7 @@ package android.graphics.drawable; +import android.content.res.ColorStateList; import android.content.res.Resources; import android.content.res.Resources.Theme; import android.graphics.Canvas; @@ -23,6 +24,7 @@ import android.graphics.ColorFilter; import android.graphics.Insets; import android.graphics.PixelFormat; import android.graphics.Rect; +import android.graphics.PorterDuff.Mode; import android.os.SystemClock; import android.util.LayoutDirection; import android.util.SparseArray; @@ -63,8 +65,6 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { private long mExitAnimationEnd; private Drawable mLastDrawable; - private Insets mInsets = Insets.NONE; - // overrides from Drawable @Override @@ -116,7 +116,10 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { */ @Override public Insets getOpticalInsets() { - return mInsets; + if (mCurrDrawable != null) { + return mCurrDrawable.getOpticalInsets(); + } + return Insets.NONE; } @Override @@ -151,7 +154,7 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { @Override public void setColorFilter(ColorFilter cf) { - mDrawableContainerState.mHasColorFilter = true; + mDrawableContainerState.mHasColorFilter = (cf != null); if (mDrawableContainerState.mColorFilter != cf) { mDrawableContainerState.mColorFilter = cf; @@ -162,6 +165,20 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { } } + @Override + public void setTint(ColorStateList tint, Mode tintMode) { + mDrawableContainerState.mHasTint = (tint != null && tintMode != null); + + if (mDrawableContainerState.mTint != tint || mDrawableContainerState.mTintMode != tintMode) { + mDrawableContainerState.mTint = tint; + mDrawableContainerState.mTintMode = tintMode; + + if (mCurrDrawable != null) { + mCurrDrawable.mutate().setTint(tint, tintMode); + } + } + } + /** * Change the global fade duration when a new drawable is entering * the scene. @@ -187,9 +204,6 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { } if (mCurrDrawable != null) { mCurrDrawable.setBounds(bounds); - - // Must obtain optical insets after setting bounds. - mInsets = mCurrDrawable.getOpticalInsets(); } } @@ -396,6 +410,8 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { } if (mDrawableContainerState.mHasColorFilter) { d.setColorFilter(mDrawableContainerState.mColorFilter); + } else if (mDrawableContainerState.mHasTint) { + d.setTint(mDrawableContainerState.mTint, mDrawableContainerState.mTintMode); } d.setVisible(isVisible(), true); d.setDither(mDrawableContainerState.mDither); @@ -404,15 +420,9 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { d.setBounds(getBounds()); d.setLayoutDirection(getLayoutDirection()); d.setAutoMirrored(mDrawableContainerState.mAutoMirrored); - - // Must obtain optical insets after setting bounds. - mInsets = d.getOpticalInsets(); - } else { - mInsets = Insets.NONE; } } else { mCurrDrawable = null; - mInsets = Insets.NONE; mCurIndex = -1; } @@ -566,6 +576,10 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { ColorFilter mColorFilter; boolean mHasColorFilter; + ColorStateList mTint; + Mode mTintMode; + boolean mHasTint; + DrawableContainerState(DrawableContainerState orig, DrawableContainer owner, Resources res) { mOwner = owner; @@ -588,6 +602,9 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { mAutoMirrored = orig.mAutoMirrored; mColorFilter = orig.mColorFilter; mHasColorFilter = orig.mHasColorFilter; + mTint = orig.mTint; + mTintMode = orig.mTintMode; + mHasTint = orig.mHasTint; // Cloning the following values may require creating futures. mConstantPadding = orig.getConstantPadding(); @@ -741,7 +758,7 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { final int N = mNumChildren; final Drawable[] drawables = mDrawables; for (int i = 0; i < N; i++) { - final Drawable d = drawables[i]; + final Drawable d = drawables[i]; if (d != null) { if (d.canApplyTheme()) { return true; diff --git a/graphics/java/android/graphics/drawable/GradientDrawable.java b/graphics/java/android/graphics/drawable/GradientDrawable.java index 005b8ef..0ccce93 100644 --- a/graphics/java/android/graphics/drawable/GradientDrawable.java +++ b/graphics/java/android/graphics/drawable/GradientDrawable.java @@ -135,18 +135,17 @@ public class GradientDrawable extends Drawable { private Paint mStrokePaint; // optional, set by the caller private ColorFilter mColorFilter; // optional, set by the caller private int mAlpha = 0xFF; // modified by the caller - private boolean mDither; private final Path mPath = new Path(); private final RectF mRect = new RectF(); private Paint mLayerPaint; // internal, used if we use saveLayer() - private boolean mRectIsDirty; // internal state + private boolean mGradientIsDirty; // internal state private boolean mMutated; private Path mRingPath; private boolean mPathIsDirty = true; - /** Current gradient radius, valid when {@link #mRectIsDirty} is false. */ + /** Current gradient radius, valid when {@link #mGradientIsDirty} is false. */ private float mGradientRadius; /** @@ -384,7 +383,7 @@ public class GradientDrawable extends Drawable { */ public void setGradientType(int gradient) { mGradientState.setGradientType(gradient); - mRectIsDirty = true; + mGradientIsDirty = true; invalidateSelf(); } @@ -403,7 +402,7 @@ public class GradientDrawable extends Drawable { */ public void setGradientCenter(float x, float y) { mGradientState.setGradientCenter(x, y); - mRectIsDirty = true; + mGradientIsDirty = true; invalidateSelf(); } @@ -421,7 +420,7 @@ public class GradientDrawable extends Drawable { */ public void setGradientRadius(float gradientRadius) { mGradientState.setGradientRadius(gradientRadius, TypedValue.COMPLEX_UNIT_PX); - mRectIsDirty = true; + mGradientIsDirty = true; invalidateSelf(); } @@ -455,7 +454,7 @@ public class GradientDrawable extends Drawable { */ public void setUseLevel(boolean useLevel) { mGradientState.mUseLevel = useLevel; - mRectIsDirty = true; + mGradientIsDirty = true; invalidateSelf(); } @@ -483,7 +482,7 @@ public class GradientDrawable extends Drawable { */ public void setOrientation(Orientation orientation) { mGradientState.mOrientation = orientation; - mRectIsDirty = true; + mGradientIsDirty = true; invalidateSelf(); } @@ -501,7 +500,7 @@ public class GradientDrawable extends Drawable { */ public void setColors(int[] colors) { mGradientState.setColors(colors); - mRectIsDirty = true; + mGradientIsDirty = true; invalidateSelf(); } @@ -543,7 +542,7 @@ public class GradientDrawable extends Drawable { if (mLayerPaint == null) { mLayerPaint = new Paint(); } - mLayerPaint.setDither(mDither); + mLayerPaint.setDither(st.mDither); mLayerPaint.setAlpha(mAlpha); mLayerPaint.setColorFilter(mColorFilter); @@ -561,14 +560,14 @@ public class GradientDrawable extends Drawable { individual paints */ mFillPaint.setAlpha(currFillAlpha); - mFillPaint.setDither(mDither); + mFillPaint.setDither(st.mDither); mFillPaint.setColorFilter(mColorFilter); - if (mColorFilter != null && mGradientState.mColorStateList == null) { + if (mColorFilter != null && st.mColorStateList == null) { mFillPaint.setColor(mAlpha << 24); } if (haveStroke) { mStrokePaint.setAlpha(currStrokeAlpha); - mStrokePaint.setDither(mDither); + mStrokePaint.setDither(st.mDither); mStrokePaint.setColorFilter(mColorFilter); } } @@ -638,10 +637,11 @@ public class GradientDrawable extends Drawable { private void buildPathIfDirty() { final GradientState st = mGradientState; - if (mPathIsDirty || mRectIsDirty) { + if (mPathIsDirty) { + ensureValidRect(); mPath.reset(); mPath.addRoundRect(mRect, st.mRadiusArray, Path.Direction.CW); - mPathIsDirty = mRectIsDirty = false; + mPathIsDirty = false; } } @@ -804,8 +804,8 @@ public class GradientDrawable extends Drawable { @Override public void setDither(boolean dither) { - if (dither != mDither) { - mDither = dither; + if (dither != mGradientState.mDither) { + mGradientState.mDither = dither; invalidateSelf(); } } @@ -829,27 +829,27 @@ public class GradientDrawable extends Drawable { super.onBoundsChange(r); mRingPath = null; mPathIsDirty = true; - mRectIsDirty = true; + mGradientIsDirty = true; } @Override protected boolean onLevelChange(int level) { super.onLevelChange(level); - mRectIsDirty = true; + mGradientIsDirty = true; mPathIsDirty = true; invalidateSelf(); return true; } /** - * This checks mRectIsDirty, and if it is true, recomputes both our drawing + * This checks mGradientIsDirty, and if it is true, recomputes both our drawing * rectangle (mRect) and the gradient itself, since it depends on our * rectangle too. * @return true if the resulting rectangle is not empty, false otherwise */ private boolean ensureValidRect() { - if (mRectIsDirty) { - mRectIsDirty = false; + if (mGradientIsDirty) { + mGradientIsDirty = false; Rect bounds = getBounds(); float inset = 0; @@ -1015,7 +1015,7 @@ public class GradientDrawable extends Drawable { state.mThemeAttrs = a.extractThemeAttrs(); state.mShape = a.getInt(R.styleable.GradientDrawable_shape, state.mShape); - mDither = a.getBoolean(R.styleable.GradientDrawable_dither, mDither); + state.mDither = a.getBoolean(R.styleable.GradientDrawable_dither, state.mDither); if (state.mShape == RING) { state.mInnerRadius = a.getDimensionPixelSize( @@ -1459,6 +1459,8 @@ public class GradientDrawable extends Drawable { public float mThicknessRatio = DEFAULT_THICKNESS_RATIO; public int mInnerRadius = -1; public int mThickness = -1; + public boolean mDither = false; + private float mCenterX = 0.5f; private float mCenterY = 0.5f; private float mGradientRadius = 0.5f; @@ -1510,6 +1512,7 @@ public class GradientDrawable extends Drawable { mThicknessRatio = state.mThicknessRatio; mInnerRadius = state.mInnerRadius; mThickness = state.mThickness; + mDither = state.mDither; mCenterX = state.mCenterX; mCenterY = state.mCenterY; mGradientRadius = state.mGradientRadius; @@ -1672,7 +1675,7 @@ public class GradientDrawable extends Drawable { initializeWithState(state); - mRectIsDirty = true; + mGradientIsDirty = true; mMutated = false; } diff --git a/graphics/java/android/graphics/drawable/InsetDrawable.java b/graphics/java/android/graphics/drawable/InsetDrawable.java index 9e0ab86..220e81c 100644 --- a/graphics/java/android/graphics/drawable/InsetDrawable.java +++ b/graphics/java/android/graphics/drawable/InsetDrawable.java @@ -19,10 +19,12 @@ package android.graphics.drawable; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; +import android.content.res.ColorStateList; import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.Resources.Theme; import android.graphics.*; +import android.graphics.PorterDuff.Mode; import android.util.AttributeSet; import android.util.Log; @@ -44,8 +46,7 @@ import java.io.IOException; * @attr ref android.R.styleable#InsetDrawable_insetTop * @attr ref android.R.styleable#InsetDrawable_insetBottom */ -public class InsetDrawable extends Drawable implements Drawable.Callback -{ +public class InsetDrawable extends Drawable implements Drawable.Callback { // Most of this is copied from ScaleDrawable. private InsetState mInsetState; private final Rect mTmpRect = new Rect(); @@ -62,13 +63,13 @@ public class InsetDrawable extends Drawable implements Drawable.Callback public InsetDrawable(Drawable drawable, int insetLeft, int insetTop, int insetRight, int insetBottom) { this(null, null); - + mInsetState.mDrawable = drawable; mInsetState.mInsetLeft = insetLeft; mInsetState.mInsetTop = insetTop; mInsetState.mInsetRight = insetRight; mInsetState.mInsetBottom = insetBottom; - + if (drawable != null) { drawable.setCallback(this); } @@ -78,7 +79,7 @@ public class InsetDrawable extends Drawable implements Drawable.Callback public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) throws XmlPullParserException, IOException { int type; - + TypedArray a = r.obtainAttributes(attrs, com.android.internal.R.styleable.InsetDrawable); @@ -168,7 +169,7 @@ public class InsetDrawable extends Drawable implements Drawable.Callback | mInsetState.mChangingConfigurations | mInsetState.mDrawable.getChangingConfigurations(); } - + @Override public boolean getPadding(Rect padding) { boolean pad = mInsetState.mDrawable.getPadding(padding); @@ -178,7 +179,7 @@ public class InsetDrawable extends Drawable implements Drawable.Callback padding.top += mInsetState.mInsetTop; padding.bottom += mInsetState.mInsetBottom; - if (pad || (mInsetState.mInsetLeft | mInsetState.mInsetRight | + if (pad || (mInsetState.mInsetLeft | mInsetState.mInsetRight | mInsetState.mInsetTop | mInsetState.mInsetBottom) != 0) { return true; } else { @@ -217,6 +218,11 @@ public class InsetDrawable extends Drawable implements Drawable.Callback mInsetState.mDrawable.setColorFilter(cf); } + @Override + public void setTint(ColorStateList tint, Mode tintMode) { + mInsetState.mDrawable.setTint(tint, tintMode); + } + /** {@hide} */ @Override public void setLayoutDirection(int layoutDirection) { @@ -227,7 +233,7 @@ public class InsetDrawable extends Drawable implements Drawable.Callback public int getOpacity() { return mInsetState.mDrawable.getOpacity(); } - + @Override public boolean isStateful() { return mInsetState.mDrawable.isStateful(); @@ -239,7 +245,12 @@ public class InsetDrawable extends Drawable implements Drawable.Callback onBoundsChange(getBounds()); return changed; } - + + @Override + protected boolean onLevelChange(int level) { + return mInsetState.mDrawable.setLevel(level); + } + @Override protected void onBoundsChange(Rect bounds) { final Rect r = mTmpRect; @@ -321,12 +332,12 @@ public class InsetDrawable extends Drawable implements Drawable.Callback public Drawable newDrawable() { return new InsetDrawable(this, null); } - + @Override public Drawable newDrawable(Resources res) { return new InsetDrawable(this, res); } - + @Override public int getChangingConfigurations() { return mChangingConfigurations; diff --git a/graphics/java/android/graphics/drawable/LayerDrawable.java b/graphics/java/android/graphics/drawable/LayerDrawable.java index 27f0a9d..5cea7c9 100644 --- a/graphics/java/android/graphics/drawable/LayerDrawable.java +++ b/graphics/java/android/graphics/drawable/LayerDrawable.java @@ -16,12 +16,14 @@ package android.graphics.drawable; +import android.content.res.ColorStateList; import android.content.res.Resources; import android.content.res.Resources.Theme; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.ColorFilter; import android.graphics.PixelFormat; +import android.graphics.PorterDuff.Mode; import android.graphics.Rect; import android.util.AttributeSet; import android.view.View; @@ -630,6 +632,15 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { } } + @Override + public void setTint(ColorStateList tint, Mode tintMode) { + final ChildDrawable[] array = mLayerState.mChildren; + final int N = mLayerState.mNum; + for (int i = 0; i < N; i++) { + array[i].mDrawable.setTint(tint, tintMode); + } + } + /** * Sets the opacity of this drawable directly, instead of collecting the * states from the layers diff --git a/graphics/java/android/graphics/drawable/MaterialProgressDrawable.java b/graphics/java/android/graphics/drawable/MaterialProgressDrawable.java index 9e56f67..c484094 100644 --- a/graphics/java/android/graphics/drawable/MaterialProgressDrawable.java +++ b/graphics/java/android/graphics/drawable/MaterialProgressDrawable.java @@ -27,8 +27,10 @@ import android.graphics.Canvas; import android.graphics.Color; import android.graphics.ColorFilter; import android.graphics.Paint; +import android.graphics.PorterDuffColorFilter; import android.graphics.Paint.Cap; import android.graphics.Paint.Style; +import android.graphics.PorterDuff.Mode; import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.RectF; @@ -67,6 +69,7 @@ class MaterialProgressDrawable extends Drawable implements Animatable { private final Ring mRing; private MaterialProgressState mState; + private PorterDuffColorFilter mTintFilter; /** Canvas rotation in degrees. */ private float mRotation; @@ -106,6 +109,8 @@ class MaterialProgressDrawable extends Drawable implements Animatable { float insets = minEdge / 2.0f - state.mInnerRadius; ring.setInsets(insets); } + + mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode); } @Override @@ -118,15 +123,21 @@ class MaterialProgressDrawable extends Drawable implements Animatable { } @Override - protected boolean onStateChange(int[] state) { - boolean changed = super.onStateChange(state); + protected boolean onStateChange(int[] stateSet) { + boolean changed = super.onStateChange(stateSet); - final int color = mState.mColor.getColorForState(state, Color.TRANSPARENT); + final MaterialProgressState state = mState; + final int color = state.mColor.getColorForState(stateSet, Color.TRANSPARENT); if (color != mRing.getColor()) { mRing.setColor(color); changed = true; } + if (state.mTint != null && state.mTintMode != null) { + mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode); + changed = true; + } + return changed; } @@ -223,11 +234,24 @@ class MaterialProgressDrawable extends Drawable implements Animatable { return mRing.getColorFilter(); } + @Override + public void setTint(ColorStateList tint, Mode tintMode) { + if (mState.mTint != tint || mState.mTintMode != tintMode) { + mState.mTint = tint; + mState.mTintMode = tintMode; + + mTintFilter = updateTintFilter(mTintFilter, tint, tintMode); + invalidateSelf(); + } + } + + @SuppressWarnings("unused") private void setRotation(float rotation) { mRotation = rotation; invalidateSelf(); } + @SuppressWarnings("unused") private float getRotation() { return mRotation; } @@ -331,6 +355,8 @@ class MaterialProgressDrawable extends Drawable implements Animatable { private int mWidth = -1; private int mHeight = -1; private ColorStateList mColor = ColorStateList.valueOf(Color.TRANSPARENT); + private ColorStateList mTint = null; + private Mode mTintMode = null; public MaterialProgressState(MaterialProgressState orig) { if (orig != null) { @@ -340,6 +366,8 @@ class MaterialProgressDrawable extends Drawable implements Animatable { mWidth = orig.mWidth; mHeight = orig.mHeight; mColor = orig.mColor; + mTint = orig.mTint; + mTintMode = orig.mTintMode; } } diff --git a/graphics/java/android/graphics/drawable/NinePatchDrawable.java b/graphics/java/android/graphics/drawable/NinePatchDrawable.java index fea68ee..28335ea 100644 --- a/graphics/java/android/graphics/drawable/NinePatchDrawable.java +++ b/graphics/java/android/graphics/drawable/NinePatchDrawable.java @@ -327,25 +327,12 @@ public class NinePatchDrawable extends Drawable { @Override public void setTint(ColorStateList tint, PorterDuff.Mode tintMode) { - mNinePatchState.mTint = tint; - mNinePatchState.mTintMode = tintMode; - computeTintFilter(); - invalidateSelf(); - } - - private void computeTintFilter() { final NinePatchState state = mNinePatchState; - if (state.mTint != null && state.mTintMode != null) { - final int color = state.mTint.getColorForState(getState(), 0); - if (mTintFilter != null) { - mTintFilter.setColor(color); - mTintFilter.setMode(state.mTintMode); - } else { - mTintFilter = new PorterDuffColorFilter(color, state.mTintMode); - } - } else { - mTintFilter = null; - } + state.mTint = tint; + state.mTintMode = tintMode; + + mTintFilter = updateTintFilter(mTintFilter, tint, tintMode); + invalidateSelf(); } @Override @@ -549,15 +536,10 @@ public class NinePatchDrawable extends Drawable { @Override protected boolean onStateChange(int[] stateSet) { - final ColorStateList tint = mNinePatchState.mTint; - if (tint != null) { - final int newColor = tint.getColorForState(stateSet, 0); - final int oldColor = mTintFilter.getColor(); - if (oldColor != newColor) { - mTintFilter.setColor(newColor); - invalidateSelf(); - return true; - } + final NinePatchState state = mNinePatchState; + if (state.mTint != null && state.mTintMode != null) { + mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode); + return true; } return false; @@ -689,7 +671,7 @@ public class NinePatchDrawable extends Drawable { mPadding = new Rect(state.mPadding); } - computeTintFilter(); + mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode); setNinePatch(state.mNinePatch); } } diff --git a/graphics/java/android/graphics/drawable/PaintDrawable.java b/graphics/java/android/graphics/drawable/PaintDrawable.java index c71cda1..a82e7b9 100644 --- a/graphics/java/android/graphics/drawable/PaintDrawable.java +++ b/graphics/java/android/graphics/drawable/PaintDrawable.java @@ -35,7 +35,7 @@ public class PaintDrawable extends ShapeDrawable { public PaintDrawable(int color) { getPaint().setColor(color); } - + /** * Specify radius for the corners of the rectangle. If this is > 0, then the * drawable is drawn in a round-rectangle, rather than a rectangle. @@ -51,7 +51,7 @@ public class PaintDrawable extends ShapeDrawable { } setCornerRadii(radii); } - + /** * Specify radii for each of the 4 corners. For each corner, the array * contains 2 values, [X_radius, Y_radius]. The corners are ordered @@ -78,9 +78,9 @@ public class PaintDrawable extends ShapeDrawable { int radius = a.getDimensionPixelSize( com.android.internal.R.styleable.DrawableCorners_radius, 0); setCornerRadius(radius); - + // now check of they have any per-corner radii - + int topLeftRadius = a.getDimensionPixelSize( com.android.internal.R.styleable.DrawableCorners_topLeftRadius, radius); int topRightRadius = a.getDimensionPixelSize( diff --git a/graphics/java/android/graphics/drawable/PictureDrawable.java b/graphics/java/android/graphics/drawable/PictureDrawable.java index cb2d8f6..6dcda1f 100644 --- a/graphics/java/android/graphics/drawable/PictureDrawable.java +++ b/graphics/java/android/graphics/drawable/PictureDrawable.java @@ -25,7 +25,7 @@ import android.graphics.Rect; /** * Drawable subclass that wraps a Picture, allowing the picture to be used - * whereever a Drawable is supported. + * wherever a Drawable is supported. */ public class PictureDrawable extends Drawable { @@ -40,7 +40,7 @@ public class PictureDrawable extends Drawable { public PictureDrawable(Picture picture) { mPicture = picture; } - + /** * Return the picture associated with the drawable. May be null. * @@ -49,7 +49,7 @@ public class PictureDrawable extends Drawable { public Picture getPicture() { return mPicture; } - + /** * Associate a picture with this drawable. The picture may be null. * @@ -58,7 +58,7 @@ public class PictureDrawable extends Drawable { public void setPicture(Picture picture) { mPicture = picture; } - + @Override public void draw(Canvas canvas) { if (mPicture != null) { @@ -86,16 +86,16 @@ public class PictureDrawable extends Drawable { // not sure, so be safe return PixelFormat.TRANSLUCENT; } - + @Override public void setFilterBitmap(boolean filter) {} - + @Override public void setDither(boolean dither) {} - + @Override public void setColorFilter(ColorFilter colorFilter) {} - + @Override public void setAlpha(int alpha) {} } diff --git a/graphics/java/android/graphics/drawable/Ripple.java b/graphics/java/android/graphics/drawable/Ripple.java index 345400e..2d49365 100644 --- a/graphics/java/android/graphics/drawable/Ripple.java +++ b/graphics/java/android/graphics/drawable/Ripple.java @@ -142,14 +142,16 @@ class Ripple { } private void clampStartingPosition() { - final float dX = mStartingX - mBounds.exactCenterX(); - final float dY = mStartingY - mBounds.exactCenterY(); + final float cX = mBounds.exactCenterX(); + final float cY = mBounds.exactCenterY(); + final float dX = mStartingX - cX; + final float dY = mStartingY - cY; final float r = mOuterRadius; if (dX * dX + dY * dY > r * r) { // Point is outside the circle, clamp to the circumference. final double angle = Math.atan2(dY, dX); - mClampedStartingX = (float) (Math.cos(angle) * r); - mClampedStartingY = (float) (Math.sin(angle) * r); + mClampedStartingX = cX + (float) (Math.cos(angle) * r); + mClampedStartingY = cY + (float) (Math.sin(angle) * r); } else { mClampedStartingX = mStartingX; mClampedStartingY = mStartingY; diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java index 0512ecc..f2e75a5 100644 --- a/graphics/java/android/graphics/drawable/RippleDrawable.java +++ b/graphics/java/android/graphics/drawable/RippleDrawable.java @@ -645,25 +645,29 @@ public class RippleDrawable extends LayerDrawable { @Override public Rect getDirtyBounds() { - final Rect drawingBounds = mDrawingBounds; - final Rect dirtyBounds = mDirtyBounds; - dirtyBounds.set(drawingBounds); - drawingBounds.setEmpty(); - - final int cX = (int) mHotspotBounds.exactCenterX(); - final int cY = (int) mHotspotBounds.exactCenterY(); - final Rect rippleBounds = mTempRect; - final Ripple[] activeRipples = mAnimatingRipples; - final int N = mAnimatingRipplesCount; - for (int i = 0; i < N; i++) { - activeRipples[i].getBounds(rippleBounds); - rippleBounds.offset(cX, cY); - drawingBounds.union(rippleBounds); - } + if (isProjected()) { + final Rect drawingBounds = mDrawingBounds; + final Rect dirtyBounds = mDirtyBounds; + dirtyBounds.set(drawingBounds); + drawingBounds.setEmpty(); + + final int cX = (int) mHotspotBounds.exactCenterX(); + final int cY = (int) mHotspotBounds.exactCenterY(); + final Rect rippleBounds = mTempRect; + final Ripple[] activeRipples = mAnimatingRipples; + final int N = mAnimatingRipplesCount; + for (int i = 0; i < N; i++) { + activeRipples[i].getBounds(rippleBounds); + rippleBounds.offset(cX, cY); + drawingBounds.union(rippleBounds); + } - dirtyBounds.union(drawingBounds); - dirtyBounds.union(super.getDirtyBounds()); - return dirtyBounds; + dirtyBounds.union(drawingBounds); + dirtyBounds.union(super.getDirtyBounds()); + return dirtyBounds; + } else { + return getBounds(); + } } @Override diff --git a/graphics/java/android/graphics/drawable/RotateDrawable.java b/graphics/java/android/graphics/drawable/RotateDrawable.java index 06aeb98..8f8fa98 100644 --- a/graphics/java/android/graphics/drawable/RotateDrawable.java +++ b/graphics/java/android/graphics/drawable/RotateDrawable.java @@ -22,6 +22,8 @@ import org.xmlpull.v1.XmlPullParserException; import android.graphics.Canvas; import android.graphics.ColorFilter; import android.graphics.Rect; +import android.graphics.PorterDuff.Mode; +import android.content.res.ColorStateList; import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.Resources.Theme; @@ -137,6 +139,11 @@ public class RotateDrawable extends Drawable implements Drawable.Callback { } @Override + public void setTint(ColorStateList tint, Mode tintMode) { + mState.mDrawable.setTint(tint, tintMode); + } + + @Override public int getOpacity() { return mState.mDrawable.getOpacity(); } diff --git a/graphics/java/android/graphics/drawable/ScaleDrawable.java b/graphics/java/android/graphics/drawable/ScaleDrawable.java index f090c11..46c92fe 100644 --- a/graphics/java/android/graphics/drawable/ScaleDrawable.java +++ b/graphics/java/android/graphics/drawable/ScaleDrawable.java @@ -19,10 +19,12 @@ package android.graphics.drawable; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; +import android.content.res.ColorStateList; import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.Resources.Theme; import android.graphics.*; +import android.graphics.PorterDuff.Mode; import android.view.Gravity; import android.util.AttributeSet; @@ -188,6 +190,11 @@ public class ScaleDrawable extends Drawable implements Drawable.Callback { } @Override + public void setTint(ColorStateList tint, Mode tintMode) { + mScaleState.mDrawable.setTint(tint, tintMode); + } + + @Override public int getOpacity() { return mScaleState.mDrawable.getOpacity(); } diff --git a/graphics/java/android/graphics/drawable/ShapeDrawable.java b/graphics/java/android/graphics/drawable/ShapeDrawable.java index 024f77c..86765dd 100644 --- a/graphics/java/android/graphics/drawable/ShapeDrawable.java +++ b/graphics/java/android/graphics/drawable/ShapeDrawable.java @@ -24,6 +24,7 @@ import android.graphics.ColorFilter; import android.graphics.Outline; import android.graphics.Paint; import android.graphics.PixelFormat; +import android.graphics.PorterDuff; import android.graphics.PorterDuff.Mode; import android.graphics.PorterDuffColorFilter; import android.graphics.Rect; @@ -32,6 +33,7 @@ import android.graphics.drawable.shapes.Shape; import android.content.res.Resources.Theme; import android.util.AttributeSet; +import com.android.internal.R; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -74,7 +76,7 @@ public class ShapeDrawable extends Drawable { * ShapeDrawable constructor. */ public ShapeDrawable() { - this((ShapeState) null); + this(new ShapeState(null), null, null); } /** @@ -83,20 +85,11 @@ public class ShapeDrawable extends Drawable { * @param s the Shape that this ShapeDrawable should be */ public ShapeDrawable(Shape s) { - this((ShapeState) null); + this(new ShapeState(null), null, null); mShapeState.mShape = s; } - private ShapeDrawable(ShapeState state) { - mShapeState = new ShapeState(state); - - if (state != null && state.mTint != null) { - final int color = state.mTint.getColorForState(getState(), 0); - mTintFilter = new PorterDuffColorFilter(color, state.mTintMode); - } - } - /** * Returns the Shape of this ShapeDrawable. */ @@ -292,31 +285,13 @@ public class ShapeDrawable extends Drawable { } @Override - public void setTint(ColorStateList tint, Mode tintMode) { - if (mShapeState.mTint != tint || mShapeState.mTintMode != tintMode) { - mShapeState.mTint = tint; - mShapeState.mTintMode = tintMode; - updateTintFilter(); - invalidateSelf(); - } - } + public void setTint(ColorStateList tint, PorterDuff.Mode tintMode) { + final ShapeState state = mShapeState; + state.mTint = tint; + state.mTintMode = tintMode; - /** - * Ensures the tint filter is consistent with the current tint color and - * mode. - */ - private void updateTintFilter() { - final ColorStateList tint = mShapeState.mTint; - final Mode tintMode = mShapeState.mTintMode; - if (tint != null && tintMode != null) { - if (mTintFilter == null) { - mTintFilter = new PorterDuffColorFilter(0, tintMode); - } else { - mTintFilter.setMode(tintMode); - } - } else { - mTintFilter = null; - } + mTintFilter = updateTintFilter(mTintFilter, tint, tintMode); + invalidateSelf(); } @Override @@ -357,17 +332,11 @@ public class ShapeDrawable extends Drawable { @Override protected boolean onStateChange(int[] stateSet) { - final ColorStateList tint = mShapeState.mTint; - if (tint != null) { - final int newColor = tint.getColorForState(stateSet, 0); - final int oldColor = mTintFilter.getColor(); - if (oldColor != newColor) { - mTintFilter.setColor(newColor); - invalidateSelf(); - return true; - } + final ShapeState state = mShapeState; + if (state.mTint != null && state.mTintMode != null) { + mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode); + return true; } - return false; } @@ -408,20 +377,8 @@ public class ShapeDrawable extends Drawable { throws XmlPullParserException, IOException { super.inflate(r, parser, attrs, theme); - TypedArray a = r.obtainAttributes(attrs, com.android.internal.R.styleable.ShapeDrawable); - - int color = mShapeState.mPaint.getColor(); - color = a.getColor(com.android.internal.R.styleable.ShapeDrawable_color, color); - mShapeState.mPaint.setColor(color); - - boolean dither = a.getBoolean(com.android.internal.R.styleable.ShapeDrawable_dither, false); - mShapeState.mPaint.setDither(dither); - - setIntrinsicWidth((int) - a.getDimension(com.android.internal.R.styleable.ShapeDrawable_width, 0f)); - setIntrinsicHeight((int) - a.getDimension(com.android.internal.R.styleable.ShapeDrawable_height, 0f)); - + final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.ShapeDrawable); + updateStateFromTypedArray(a); a.recycle(); int type; @@ -439,6 +396,57 @@ public class ShapeDrawable extends Drawable { " for ShapeDrawable " + this); } } + + // Update local properties. + initializeWithState(mShapeState, r); + } + + @Override + public void applyTheme(Theme t) { + super.applyTheme(t); + + final ShapeState state = mShapeState; + if (state == null || state.mThemeAttrs == null) { + return; + } + + final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.ShapeDrawable); + updateStateFromTypedArray(a); + a.recycle(); + + // Update local properties. + initializeWithState(state, t.getResources()); + } + + private void updateStateFromTypedArray(TypedArray a) { + final ShapeState state = mShapeState; + final Paint paint = state.mPaint; + + // Extract the theme attributes, if any. + state.mThemeAttrs = a.extractThemeAttrs(); + + int color = paint.getColor(); + color = a.getColor(R.styleable.ShapeDrawable_color, color); + paint.setColor(color); + + boolean dither = paint.isDither(); + dither = a.getBoolean(R.styleable.ShapeDrawable_dither, dither); + paint.setDither(dither); + + setIntrinsicWidth((int) a.getDimension( + R.styleable.ShapeDrawable_width, state.mIntrinsicWidth)); + setIntrinsicHeight((int) a.getDimension( + R.styleable.ShapeDrawable_height, state.mIntrinsicHeight)); + + final int tintMode = a.getInt(R.styleable.ShapeDrawable_tintMode, -1); + if (tintMode != -1) { + state.mTintMode = Drawable.parseTintMode(tintMode, Mode.SRC_IN); + } + + final ColorStateList tint = a.getColorStateList(R.styleable.ShapeDrawable_tint); + if (tint != null) { + state.mTint = tint; + } } private void updateShape() { @@ -498,6 +506,7 @@ public class ShapeDrawable extends Drawable { * Defines the intrinsic properties of this ShapeDrawable's Shape. */ final static class ShapeState extends ConstantState { + int[] mThemeAttrs; int mChangingConfigurations; Paint mPaint; Shape mShape; @@ -511,6 +520,7 @@ public class ShapeDrawable extends Drawable { ShapeState(ShapeState orig) { if (orig != null) { + mThemeAttrs = orig.mThemeAttrs; mPaint = orig.mPaint; mShape = orig.mShape; mTint = orig.mTint; @@ -526,13 +536,23 @@ public class ShapeDrawable extends Drawable { } @Override + public boolean canApplyTheme() { + return mThemeAttrs != null; + } + + @Override public Drawable newDrawable() { - return new ShapeDrawable(this); + return new ShapeDrawable(this, null, null); } @Override public Drawable newDrawable(Resources res) { - return new ShapeDrawable(this); + return new ShapeDrawable(this, res, null); + } + + @Override + public Drawable newDrawable(Resources res, Theme theme) { + return new ShapeDrawable(this, res, theme); } @Override @@ -542,6 +562,30 @@ 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; + } + + initializeWithState(state, res); + } + + /** + * Initializes local dynamic properties from state. This should be called + * after significant state changes, e.g. from the One True Constructor and + * after inflating or applying a theme. + */ + private void initializeWithState(ShapeState state, Resources res) { + mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode); + } + + /** * Base class defines a factory object that is called each time the drawable * is resized (has a new width or height). Its resize() method returns a * corresponding shader, or null. Implement this class if you'd like your diff --git a/graphics/java/android/graphics/drawable/StateListDrawable.java b/graphics/java/android/graphics/drawable/StateListDrawable.java index b2fac9b..f359fdd 100644 --- a/graphics/java/android/graphics/drawable/StateListDrawable.java +++ b/graphics/java/android/graphics/drawable/StateListDrawable.java @@ -235,10 +235,10 @@ public class StateListDrawable extends DrawableContainer { public Drawable getStateDrawable(int index) { return mStateListState.getChild(index); } - + /** * Gets the index of the drawable with the provided state set. - * + * * @param stateSet the state set to look up * @return the index of the provided state set, or -1 if not found * @hide pending API council @@ -248,7 +248,7 @@ public class StateListDrawable extends DrawableContainer { public int getStateDrawableIndex(int[] stateSet) { return mStateListState.indexOfStateSet(stateSet); } - + @Override public Drawable mutate() { if (!mMutated && super.mutate() == this) { diff --git a/graphics/java/android/graphics/drawable/TransitionDrawable.java b/graphics/java/android/graphics/drawable/TransitionDrawable.java index 622e90b..4380ca4 100644 --- a/graphics/java/android/graphics/drawable/TransitionDrawable.java +++ b/graphics/java/android/graphics/drawable/TransitionDrawable.java @@ -42,20 +42,20 @@ import android.os.SystemClock; public class TransitionDrawable extends LayerDrawable implements Drawable.Callback { /** - * A transition is about to start. + * A transition is about to start. */ private static final int TRANSITION_STARTING = 0; - + /** * The transition has started and the animation is in progress */ private static final int TRANSITION_RUNNING = 1; - + /** * No transition will be applied */ private static final int TRANSITION_NONE = 2; - + /** * The current state of the transition. One of {@link #TRANSITION_STARTING}, * {@link #TRANSITION_RUNNING} and {@link #TRANSITION_NONE} @@ -101,10 +101,10 @@ public class TransitionDrawable extends LayerDrawable implements Drawable.Callba LayerState createConstantState(LayerState state, Resources res) { return new TransitionState((TransitionState) state, this, res); } - + /** * Begin the second layer on top of the first layer. - * + * * @param durationMillis The length of the transition in milliseconds */ public void startTransition(int durationMillis) { @@ -116,7 +116,7 @@ public class TransitionDrawable extends LayerDrawable implements Drawable.Callba mTransitionState = TRANSITION_STARTING; invalidateSelf(); } - + /** * Show only the first layer. */ @@ -184,7 +184,7 @@ public class TransitionDrawable extends LayerDrawable implements Drawable.Callba } break; } - + final int alpha = mAlpha; final boolean crossFade = mCrossFade; final ChildDrawable[] array = mLayerState.mChildren; @@ -217,7 +217,7 @@ public class TransitionDrawable extends LayerDrawable implements Drawable.Callba d.draw(canvas); d.setAlpha(0xFF); } - + if (!done) { invalidateSelf(); } diff --git a/graphics/java/android/graphics/drawable/VectorDrawable.java b/graphics/java/android/graphics/drawable/VectorDrawable.java index 9a63fa3..c98f2a1 100644 --- a/graphics/java/android/graphics/drawable/VectorDrawable.java +++ b/graphics/java/android/graphics/drawable/VectorDrawable.java @@ -14,20 +14,26 @@ package android.graphics.drawable; +import android.content.res.ColorStateList; import android.content.res.Resources; import android.content.res.Resources.Theme; import android.content.res.TypedArray; import android.graphics.Canvas; +import android.graphics.Color; import android.graphics.ColorFilter; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Path; import android.graphics.PathMeasure; import android.graphics.PixelFormat; +import android.graphics.PorterDuffColorFilter; import android.graphics.Rect; import android.graphics.Region; +import android.graphics.PorterDuff.Mode; +import android.util.ArrayMap; import android.util.AttributeSet; import android.util.Log; +import android.util.PathParser; import android.util.Xml; import com.android.internal.R; @@ -39,8 +45,7 @@ import org.xmlpull.v1.XmlPullParserFactory; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collection; -import java.util.HashMap; +import java.util.Stack; /** * This lets you create a drawable based on an XML vector graphic It can be @@ -58,9 +63,26 @@ import java.util.HashMap; * <dd>Used to defined the size of the virtual canvas the paths are drawn on. * The size is defined using the attributes <code>android:viewportHeight</code> * <code>android:viewportWidth</code></dd> + * <dt><code><group></code></dt> + * <dd>Defines a group of paths or subgroups, plus transformation information. + * The transformations are defined in the same coordinates as the viewport. + * And the transformations are applied in the order of scale, rotate then translate. </dd> + * <dt><code>android:rotation</code> + * <dd>The degrees of rotation of the group.</dd></dt> + * <dt><code>android:pivotX</code> + * <dd>The X coordinate of the pivot for the scale and rotation of the group</dd></dt> + * <dt><code>android:pivotY</code> + * <dd>The Y coordinate of the pivot for the scale and rotation of the group</dd></dt> + * <dt><code>android:scaleX</code> + * <dd>The amount of scale on the X Coordinate</dd></dt> + * <dt><code>android:scaleY</code> + * <dd>The amount of scale on the Y coordinate</dd></dt> + * <dt><code>android:translateX</code> + * <dd>The amount of translation on the X coordinate</dd></dt> + * <dt><code>android:translateY</code> + * <dd>The amount of translation on the Y coordinate</dd></dt> * <dt><code><path></code></dt> - * <dd>Defines paths to be drawn. Multiple paths can be defined in one xml file. - * The paths are drawn in the order of their definition order. + * <dd>Defines paths to be drawn. * <dl> * <dt><code>android:name</code> * <dd>Defines the name of the path.</dd></dt> @@ -76,12 +98,6 @@ import java.util.HashMap; * <dd>The width a path stroke</dd></dt> * <dt><code>android:strokeOpacity</code> * <dd>The opacity of a path stroke</dd></dt> - * <dt><code>android:rotation</code> - * <dd>The amount to rotation the path stroke.</dd></dt> - * <dt><code>android:pivotX</code> - * <dd>The X coordinate of the center of rotation of a path</dd></dt> - * <dt><code>android:pivotY</code> - * <dd>The Y coordinate of the center of rotation of a path</dd></dt> * <dt><code>android:fillOpacity</code> * <dd>The opacity to fill the path with</dd></dt> * <dt><code>android:trimPathStart</code> @@ -101,13 +117,13 @@ import java.util.HashMap; * <dd>Sets the Miter limit for a stroked path</dd></dt> * </dl> * </dd> - * @hide */ public class VectorDrawable extends Drawable { private static final String LOGTAG = VectorDrawable.class.getSimpleName(); private static final String SHAPE_SIZE = "size"; private static final String SHAPE_VIEWPORT = "viewport"; + private static final String SHAPE_GROUP = "group"; private static final String SHAPE_PATH = "path"; private static final String SHAPE_VECTOR = "vector"; @@ -119,20 +135,33 @@ public class VectorDrawable extends Drawable { private static final int LINEJOIN_ROUND = 1; private static final int LINEJOIN_BEVEL = 2; + private static final boolean DBG_VECTOR_DRAWABLE = false; + private final VectorDrawableState mVectorState; - private int mAlpha = 0xFF; + private final ArrayMap<String, Object> mVGTargetsMap = new ArrayMap<String, Object>(); + + private PorterDuffColorFilter mTintFilter; public VectorDrawable() { mVectorState = new VectorDrawableState(null); } private VectorDrawable(VectorDrawableState state, Resources res, Theme theme) { - mVectorState = new VectorDrawableState(state); - - if (theme != null && canApplyTheme()) { + if (theme != null && state.canApplyTheme()) { + // If we need to apply a theme, implicitly mutate. + mVectorState = new VectorDrawableState(state); applyTheme(theme); + } else { + mVectorState = state; } + + mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode); + mVectorState.mVPathRenderer.setColorFilter(mTintFilter); + } + + Object getTargetByName(String name) { + return mVGTargetsMap.get(name); } @Override @@ -150,78 +179,72 @@ public class VectorDrawable extends Drawable { } @Override + public int getAlpha() { + return mVectorState.mVPathRenderer.getRootAlpha(); + } + + @Override public void setAlpha(int alpha) { - // TODO correct handling of transparent - if (mAlpha != alpha) { - mAlpha = alpha; + if (mVectorState.mVPathRenderer.getRootAlpha() != alpha) { + mVectorState.mVPathRenderer.setRootAlpha(alpha); invalidateSelf(); } } @Override public void setColorFilter(ColorFilter colorFilter) { - mVectorState.mVPathRenderer.setColorFilter(colorFilter); + final VectorDrawableState state = mVectorState; + if (colorFilter != null) { + // Color filter overrides tint. + mTintFilter = null; + } else if (state.mTint != null && state.mTintMode != null) { + // Restore the tint filter, if we need one. + final int color = state.mTint.getColorForState(getState(), Color.TRANSPARENT); + mTintFilter = new PorterDuffColorFilter(color, state.mTintMode); + colorFilter = mTintFilter; + } + + state.mVPathRenderer.setColorFilter(colorFilter); invalidateSelf(); } @Override - public int getOpacity() { - return PixelFormat.TRANSLUCENT; - } - - /** - * Sets padding for this shape, defined by a Rect object. Define the padding - * in the Rect object as: left, top, right, bottom. - */ - public void setPadding(Rect padding) { - setPadding(padding.left, padding.top, padding.right, padding.bottom); - } + public void setTint(ColorStateList tint, Mode tintMode) { + final VectorDrawableState state = mVectorState; + if (state.mTint != tint || state.mTintMode != tintMode) { + state.mTint = tint; + state.mTintMode = tintMode; - /** - * Sets padding for the shape. - * - * @param left padding for the left side (in pixels) - * @param top padding for the top (in pixels) - * @param right padding for the right side (in pixels) - * @param bottom padding for the bottom (in pixels) - */ - public void setPadding(int left, int top, int right, int bottom) { - if ((left | top | right | bottom) == 0) { - mVectorState.mPadding = null; - } else { - if (mVectorState.mPadding == null) { - mVectorState.mPadding = new Rect(); - } - mVectorState.mPadding.set(left, top, right, bottom); + mTintFilter = updateTintFilter(mTintFilter, tint, tintMode); + mVectorState.mVPathRenderer.setColorFilter(mTintFilter); + invalidateSelf(); } - invalidateSelf(); } @Override - public int getIntrinsicWidth() { - return (int) mVectorState.mVPathRenderer.mBaseWidth; + protected boolean onStateChange(int[] stateSet) { + final VectorDrawableState state = mVectorState; + if (state.mTint != null && state.mTintMode != null) { + mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode); + mVectorState.mVPathRenderer.setColorFilter(mTintFilter); + return true; + } + return false; } @Override - public int getIntrinsicHeight() { - return (int) mVectorState.mVPathRenderer.mBaseHeight; + public int getOpacity() { + return PixelFormat.TRANSLUCENT; } @Override - public boolean getPadding(Rect padding) { - if (mVectorState.mPadding != null) { - padding.set(mVectorState.mPadding); - return true; - } else { - return super.getPadding(padding); - } + public int getIntrinsicWidth() { + return (int) mVectorState.mVPathRenderer.mBaseWidth; } @Override - public void inflate(Resources res, XmlPullParser parser, AttributeSet attrs, Theme theme) - throws XmlPullParserException, IOException { - final VPathRenderer p = inflateInternal(res, parser, attrs, theme); - setPathRenderer(p); + public int getIntrinsicHeight() { + return (int) mVectorState.mVPathRenderer.mBaseHeight; } @Override @@ -260,24 +283,71 @@ public class VectorDrawable extends Drawable { return null; } - private VPathRenderer inflateInternal(Resources res, XmlPullParser parser, AttributeSet attrs, - Theme theme) throws XmlPullParserException, IOException { + private static int applyAlpha(int color, float alpha) { + int alphaBytes = Color.alpha(color); + color &= 0x00FFFFFF; + color |= ((int) (alphaBytes * alpha)) << 24; + return color; + } + + + @Override + public void inflate(Resources res, XmlPullParser parser, AttributeSet attrs, Theme theme) + throws XmlPullParserException, IOException { + final TypedArray a = obtainAttributes(res, theme, attrs,R.styleable.VectorDrawable); + updateStateFromTypedArray(a); + a.recycle(); + + final VectorDrawableState state = mVectorState; + state.mVPathRenderer = inflateInternal(res, parser, attrs, theme); + + mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode); + state.mVPathRenderer.setColorFilter(mTintFilter); + } + + private void updateStateFromTypedArray(TypedArray a) { + final VectorDrawableState state = mVectorState; + + // Extract the theme attributes, if any. + state.mThemeAttrs = a.extractThemeAttrs(); + + final int tintMode = a.getInt(R.styleable.VectorDrawable_tintMode, -1); + if (tintMode != -1) { + state.mTintMode = Drawable.parseTintMode(tintMode, Mode.SRC_IN); + } + + final ColorStateList tint = a.getColorStateList(R.styleable.VectorDrawable_tint); + if (tint != null) { + state.mTint = tint; + } + } + + private VPathRenderer inflateInternal(Resources res, XmlPullParser parser, AttributeSet attrs, Theme theme) + throws XmlPullParserException, IOException { final VPathRenderer pathRenderer = new VPathRenderer(); boolean noSizeTag = true; boolean noViewportTag = true; boolean noPathTag = true; - VGroup currentGroup = new VGroup(); + // Use a stack to help to build the group tree. + // The top of the stack is always the current group. + final Stack<VGroup> groupStack = new Stack<VGroup>(); + groupStack.push(pathRenderer.mRootGroup); int eventType = parser.getEventType(); while (eventType != XmlPullParser.END_DOCUMENT) { if (eventType == XmlPullParser.START_TAG) { final String tagName = parser.getName(); + final VGroup currentGroup = groupStack.peek(); + if (SHAPE_PATH.equals(tagName)) { final VPath path = new VPath(); path.inflate(res, attrs, theme); currentGroup.add(path); + if (path.getPathName() != null) { + mVGTargetsMap.put(path.getPathName(), path); + } noPathTag = false; } else if (SHAPE_SIZE.equals(tagName)) { pathRenderer.parseSize(res, attrs); @@ -285,12 +355,29 @@ public class VectorDrawable extends Drawable { } else if (SHAPE_VIEWPORT.equals(tagName)) { pathRenderer.parseViewport(res, attrs); noViewportTag = false; + } else if (SHAPE_GROUP.equals(tagName)) { + VGroup newChildGroup = new VGroup(); + newChildGroup.inflate(res, attrs, theme); + currentGroup.mChildGroupList.add(newChildGroup); + groupStack.push(newChildGroup); + if (newChildGroup.getGroupName() != null) { + mVGTargetsMap.put(newChildGroup.getGroupName(), newChildGroup); + } + } + } else if (eventType == XmlPullParser.END_TAG) { + final String tagName = parser.getName(); + if (SHAPE_GROUP.equals(tagName)) { + groupStack.pop(); } } - eventType = parser.next(); } + // Print the tree out for debug. + if (DBG_VECTOR_DRAWABLE) { + printGroupTree(pathRenderer.mRootGroup, 0); + } + if (noSizeTag || noViewportTag || noPathTag) { final StringBuffer tag = new StringBuffer(); @@ -315,26 +402,39 @@ public class VectorDrawable extends Drawable { throw new XmlPullParserException("no " + tag + " defined"); } - pathRenderer.mCurrentGroup = currentGroup; - // post parse cleanup - pathRenderer.parseFinish(); return pathRenderer; } - private void setPathRenderer(VPathRenderer pathRenderer) { - mVectorState.mVPathRenderer = pathRenderer; + private void printGroupTree(VGroup currentGroup, int level) { + String indent = ""; + for (int i = 0 ; i < level ; i++) { + indent += " "; + } + // Print the current node + Log.v(LOGTAG, indent + "current group is :" + currentGroup.getGroupName() + + " rotation is " + currentGroup.mRotate); + Log.v(LOGTAG, indent + "matrix is :" + currentGroup.getLocalMatrix().toString()); + // Then print all the children + for (int i = 0 ; i < currentGroup.mChildGroupList.size(); i++) { + printGroupTree(currentGroup.mChildGroupList.get(i), level + 1); + } } private static class VectorDrawableState extends ConstantState { + int[] mThemeAttrs; int mChangingConfigurations; VPathRenderer mVPathRenderer; - Rect mPadding; + ColorStateList mTint; + Mode mTintMode; public VectorDrawableState(VectorDrawableState copy) { if (copy != null) { + mThemeAttrs = copy.mThemeAttrs; mChangingConfigurations = copy.mChangingConfigurations; + // TODO: Make sure the constant state are handled correctly. mVPathRenderer = new VPathRenderer(copy.mVPathRenderer); - mPadding = new Rect(copy.mPadding); + mTint = copy.mTint; + mTintMode = copy.mTintMode; } } @@ -360,35 +460,51 @@ public class VectorDrawable extends Drawable { } private static class VPathRenderer { + /* Right now the internal data structure is organized as a tree. + * Each node can be a group node, or a path. + * A group node can have groups or paths as children, but a path node has + * no children. + * One example can be: + * Root Group + * / | \ + * Group Path Group + * / \ | + * Path Path Path + * + */ + private final VGroup mRootGroup; + private final Path mPath = new Path(); private final Path mRenderPath = new Path(); - private final Matrix mMatrix = new Matrix(); + private static final Matrix IDENTITY_MATRIX = new Matrix(); - private VPath[] mCurrentPaths; private Paint mStrokePaint; private Paint mFillPaint; private ColorFilter mColorFilter; private PathMeasure mPathMeasure; - private VGroup mCurrentGroup = new VGroup(); + private float mBaseWidth = 0; + private float mBaseHeight = 0; + private float mViewportWidth = 0; + private float mViewportHeight = 0; + private int mRootAlpha = 0xFF; - float mBaseWidth = 0; - float mBaseHeight = 0; - float mViewportWidth = 0; - float mViewportHeight = 0; + private final Matrix mFinalPathMatrix = new Matrix(); public VPathRenderer() { + mRootGroup = new VGroup(); } - public VPathRenderer(VPathRenderer copy) { - mCurrentGroup = copy.mCurrentGroup; - if (copy.mCurrentPaths != null) { - mCurrentPaths = new VPath[copy.mCurrentPaths.length]; - for (int i = 0; i < mCurrentPaths.length; i++) { - mCurrentPaths[i] = new VPath(copy.mCurrentPaths[i]); - } - } + public void setRootAlpha(int alpha) { + mRootAlpha = alpha; + } + + public int getRootAlpha() { + return mRootAlpha; + } + public VPathRenderer(VPathRenderer copy) { + mRootGroup = copy.mRootGroup; mBaseWidth = copy.mBaseWidth; mBaseHeight = copy.mBaseHeight; mViewportWidth = copy.mViewportHeight; @@ -396,24 +512,59 @@ public class VectorDrawable extends Drawable { } public boolean canApplyTheme() { - final ArrayList<VPath> paths = mCurrentGroup.mVGList; + // If one of the paths can apply theme, then return true; + return recursiveCanApplyTheme(mRootGroup); + } + + private boolean recursiveCanApplyTheme(VGroup currentGroup) { + // We can do a tree traverse here, if there is one path return true, + // then we return true for the whole tree. + final ArrayList<VPath> paths = currentGroup.mPathList; for (int j = paths.size() - 1; j >= 0; j--) { final VPath path = paths.get(j); if (path.canApplyTheme()) { return true; } } + + final ArrayList<VGroup> childGroups = currentGroup.mChildGroupList; + + for (int i = 0; i < childGroups.size(); i++) { + VGroup childGroup = childGroups.get(i); + if (childGroup.canApplyTheme() + || recursiveCanApplyTheme(childGroup)) { + return true; + } + } return false; } public void applyTheme(Theme t) { - final ArrayList<VPath> paths = mCurrentGroup.mVGList; + // Apply theme to every path of the tree. + recursiveApplyTheme(mRootGroup, t); + } + + private void recursiveApplyTheme(VGroup currentGroup, Theme t) { + // We can do a tree traverse here, apply theme to all paths which + // can apply theme. + final ArrayList<VPath> paths = currentGroup.mPathList; for (int j = paths.size() - 1; j >= 0; j--) { final VPath path = paths.get(j); if (path.canApplyTheme()) { path.applyTheme(t); } } + + final ArrayList<VGroup> childGroups = currentGroup.mChildGroupList; + + for (int i = 0; i < childGroups.size(); i++) { + VGroup childGroup = childGroups.get(i); + if (childGroup.canApplyTheme()) { + childGroup.applyTheme(t); + } + recursiveApplyTheme(childGroup, t); + } + } public void setColorFilter(ColorFilter colorFilter) { @@ -426,107 +577,110 @@ public class VectorDrawable extends Drawable { if (mStrokePaint != null) { mStrokePaint.setColorFilter(colorFilter); } + } - public void draw(Canvas canvas, int w, int h) { - if (mCurrentPaths == null) { - Log.e(LOGTAG,"mCurrentPaths == null"); - return; + private void drawGroupTree(VGroup currentGroup, Matrix currentMatrix, + float currentAlpha, Canvas canvas, int w, int h) { + // Calculate current group's matrix by preConcat the parent's and + // and the current one on the top of the stack. + // Basically the Mfinal = Mviewport * M0 * M1 * M2; + // Mi the local matrix at level i of the group tree. + currentGroup.mStackedMatrix.set(currentMatrix); + + currentGroup.mStackedMatrix.preConcat(currentGroup.mLocalMatrix); + + float stackedAlpha = currentAlpha * currentGroup.mGroupAlpha; + drawPath(currentGroup, stackedAlpha, canvas, w, h); + // Draw the group tree in post order. + for (int i = 0 ; i < currentGroup.mChildGroupList.size(); i++) { + drawGroupTree(currentGroup.mChildGroupList.get(i), + currentGroup.mStackedMatrix, stackedAlpha, canvas, w, h); } + } - for (int i = 0; i < mCurrentPaths.length; i++) { - if (mCurrentPaths[i] != null) { - drawPath(mCurrentPaths[i], canvas, w, h); - } - } + public void draw(Canvas canvas, int w, int h) { + // Travese the tree in pre-order to draw. + drawGroupTree(mRootGroup, IDENTITY_MATRIX, ((float) mRootAlpha) / 0xFF, canvas, w, h); } - private void drawPath(VPath vPath, Canvas canvas, int w, int h) { + private void drawPath(VGroup vGroup, float stackedAlpha, Canvas canvas, int w, int h) { final float scale = Math.min(h / mViewportHeight, w / mViewportWidth); - vPath.toPath(mPath); - final Path path = mPath; + mFinalPathMatrix.set(vGroup.mStackedMatrix); + mFinalPathMatrix.postScale(scale, scale, mViewportWidth / 2f, mViewportHeight / 2f); + mFinalPathMatrix.postTranslate(w / 2f - mViewportWidth / 2f, h / 2f - mViewportHeight / 2f); - if (vPath.mTrimPathStart != 0.0f || vPath.mTrimPathEnd != 1.0f) { - float start = (vPath.mTrimPathStart + vPath.mTrimPathOffset) % 1.0f; - float end = (vPath.mTrimPathEnd + vPath.mTrimPathOffset) % 1.0f; + ArrayList<VPath> paths = vGroup.getPaths(); + for (int i = 0; i < paths.size(); i++) { + VPath vPath = paths.get(i); + vPath.toPath(mPath); + final Path path = mPath; - if (mPathMeasure == null) { - mPathMeasure = new PathMeasure(); - } - mPathMeasure.setPath(mPath, false); - - float len = mPathMeasure.getLength(); - start = start * len; - end = end * len; - path.reset(); - if (start > end) { - mPathMeasure.getSegment(start, len, path, true); - mPathMeasure.getSegment(0f, end, path, true); - } else { - mPathMeasure.getSegment(start, end, path, true); - } - path.rLineTo(0, 0); // fix bug in measure - } + if (vPath.mTrimPathStart != 0.0f || vPath.mTrimPathEnd != 1.0f) { + float start = (vPath.mTrimPathStart + vPath.mTrimPathOffset) % 1.0f; + float end = (vPath.mTrimPathEnd + vPath.mTrimPathOffset) % 1.0f; - mRenderPath.reset(); - mMatrix.reset(); + if (mPathMeasure == null) { + mPathMeasure = new PathMeasure(); + } + mPathMeasure.setPath(mPath, false); + + float len = mPathMeasure.getLength(); + start = start * len; + end = end * len; + path.reset(); + if (start > end) { + mPathMeasure.getSegment(start, len, path, true); + mPathMeasure.getSegment(0f, end, path, true); + } else { + mPathMeasure.getSegment(start, end, path, true); + } + path.rLineTo(0, 0); // fix bug in measure + } - mMatrix.postRotate(vPath.mRotate, vPath.mPivotX, vPath.mPivotY); - mMatrix.postScale(scale, scale, mViewportWidth / 2f, mViewportHeight / 2f); - mMatrix.postTranslate(w / 2f - mViewportWidth / 2f, h / 2f - mViewportHeight / 2f); + mRenderPath.reset(); - mRenderPath.addPath(path, mMatrix); + mRenderPath.addPath(path, mFinalPathMatrix); - if (vPath.mClip) { - canvas.clipPath(mRenderPath, Region.Op.REPLACE); - } + if (vPath.mClip) { + canvas.clipPath(mRenderPath, Region.Op.REPLACE); + } else { + if (vPath.mFillColor != 0) { + if (mFillPaint == null) { + mFillPaint = new Paint(); + mFillPaint.setColorFilter(mColorFilter); + mFillPaint.setStyle(Paint.Style.FILL); + mFillPaint.setAntiAlias(true); + } + mFillPaint.setColor(applyAlpha(vPath.mFillColor, stackedAlpha)); + canvas.drawPath(mRenderPath, mFillPaint); + } - if (vPath.mFillColor != 0) { - if (mFillPaint == null) { - mFillPaint = new Paint(); - mFillPaint.setColorFilter(mColorFilter); - mFillPaint.setStyle(Paint.Style.FILL); - mFillPaint.setAntiAlias(true); - } + if (vPath.mStrokeColor != 0) { + if (mStrokePaint == null) { + mStrokePaint = new Paint(); + mStrokePaint.setColorFilter(mColorFilter); + mStrokePaint.setStyle(Paint.Style.STROKE); + mStrokePaint.setAntiAlias(true); + } - mFillPaint.setColor(vPath.mFillColor); - canvas.drawPath(mRenderPath, mFillPaint); - } + final Paint strokePaint = mStrokePaint; + if (vPath.mStrokeLineJoin != null) { + strokePaint.setStrokeJoin(vPath.mStrokeLineJoin); + } - if (vPath.mStrokeColor != 0) { - if (mStrokePaint == null) { - mStrokePaint = new Paint(); - mStrokePaint.setColorFilter(mColorFilter); - mStrokePaint.setStyle(Paint.Style.STROKE); - mStrokePaint.setAntiAlias(true); - } + if (vPath.mStrokeLineCap != null) { + strokePaint.setStrokeCap(vPath.mStrokeLineCap); + } - final Paint strokePaint = mStrokePaint; - if (vPath.mStrokeLineJoin != null) { - strokePaint.setStrokeJoin(vPath.mStrokeLineJoin); - } + strokePaint.setStrokeMiter(vPath.mStrokeMiterlimit * scale); - if (vPath.mStrokeLineCap != null) { - strokePaint.setStrokeCap(vPath.mStrokeLineCap); + strokePaint.setColor(applyAlpha(vPath.mStrokeColor, stackedAlpha)); + strokePaint.setStrokeWidth(vPath.mStrokeWidth * scale); + canvas.drawPath(mRenderPath, strokePaint); + } } - - strokePaint.setStrokeMiter(vPath.mStrokeMiterlimit * scale); - strokePaint.setColor(vPath.mStrokeColor); - strokePaint.setStrokeWidth(vPath.mStrokeWidth * scale); - canvas.drawPath(mRenderPath, strokePaint); - } - } - - /** - * Build the "current" path based on the current group - * TODO: improve memory use & performance or move to C++ - */ - public void parseFinish() { - final Collection<VPath> paths = mCurrentGroup.getPaths(); - mCurrentPaths = paths.toArray(new VPath[paths.size()]); - for (int i = 0; i < mCurrentPaths.length; i++) { - mCurrentPaths[i] = new VPath(mCurrentPaths[i]); } } @@ -566,43 +720,233 @@ public class VectorDrawable extends Drawable { } - private static class VGroup { - private final HashMap<String, VPath> mVGPathMap = new HashMap<String, VPath>(); - private final ArrayList<VPath> mVGList = new ArrayList<VPath>(); + static class VGroup { + private final ArrayList<VPath> mPathList = new ArrayList<VPath>(); + private final ArrayList<VGroup> mChildGroupList = new ArrayList<VGroup>(); + + private float mRotate = 0; + private float mPivotX = 0; + private float mPivotY = 0; + private float mScaleX = 1; + private float mScaleY = 1; + private float mTranslateX = 0; + private float mTranslateY = 0; + private float mGroupAlpha = 1; + + // mLocalMatrix is parsed from the XML. + private final Matrix mLocalMatrix = new Matrix(); + // mStackedMatrix is only used when drawing, it combines all the + // parents' local matrices with the current one. + private final Matrix mStackedMatrix = new Matrix(); + + private int[] mThemeAttrs; + + private String mGroupName = null; + + /* Getter and Setter */ + public float getRotation() { + return mRotate; + } + + public void setRotation(float rotation) { + if (rotation != mRotate) { + mRotate = rotation; + updateLocalMatrix(); + } + } + + public float getPivotX() { + return mPivotX; + } + + public void setPivotX(float pivotX) { + if (pivotX != mPivotX) { + mPivotX = pivotX; + updateLocalMatrix(); + } + } + + public float getPivotY() { + return mPivotY; + } + + public void setPivotY(float pivotY) { + if (pivotY != mPivotY) { + mPivotY = pivotY; + updateLocalMatrix(); + } + } + + public float getScaleX() { + return mScaleX; + } + + public void setScaleX(float scaleX) { + if (scaleX != mScaleX) { + mScaleX = scaleX; + updateLocalMatrix(); + } + } + + public float getScaleY() { + return mScaleY; + } + + public void setScaleY(float scaleY) { + if (scaleY != mScaleY) { + mScaleY = scaleY; + updateLocalMatrix(); + } + } + + public float getTranslateX() { + return mTranslateX; + } + + public void setTranslateX(float translateX) { + if (translateX != mTranslateX) { + mTranslateX = translateX; + updateLocalMatrix(); + } + } + + public float getTranslateY() { + return mTranslateY; + } + + public void setTranslateY(float translateY) { + if (translateY != mTranslateY) { + mTranslateY = translateY; + updateLocalMatrix(); + } + } + + public float getAlpha() { + return mGroupAlpha; + } + + public void setAlpha(float groupAlpha) { + if (groupAlpha != mGroupAlpha) { + mGroupAlpha = groupAlpha; + } + } + + public String getGroupName() { + return mGroupName; + } + + public Matrix getLocalMatrix() { + return mLocalMatrix; + } public void add(VPath path) { - String id = path.getID(); - mVGPathMap.put(id, path); - mVGList.add(path); + mPathList.add(path); } + public boolean canApplyTheme() { + return mThemeAttrs != null; + } + + public void applyTheme(Theme t) { + if (mThemeAttrs == null) { + return; + } + + final TypedArray a = t.resolveAttributes( + mThemeAttrs, R.styleable.VectorDrawablePath); + + mRotate = a.getFloat(R.styleable.VectorDrawableGroup_rotation, mRotate); + mPivotX = a.getFloat(R.styleable.VectorDrawableGroup_pivotX, mPivotX); + mPivotY = a.getFloat(R.styleable.VectorDrawableGroup_pivotY, mPivotY); + mScaleX = a.getFloat(R.styleable.VectorDrawableGroup_scaleX, mScaleX); + mScaleY = a.getFloat(R.styleable.VectorDrawableGroup_scaleY, mScaleY); + mTranslateX = a.getFloat(R.styleable.VectorDrawableGroup_translateX, mTranslateX); + mTranslateY = a.getFloat(R.styleable.VectorDrawableGroup_translateY, mTranslateY); + mGroupAlpha = a.getFloat(R.styleable.VectorDrawableGroup_alpha, mGroupAlpha); + updateLocalMatrix(); + if (a.hasValue(R.styleable.VectorDrawableGroup_name)) { + mGroupName = a.getString(R.styleable.VectorDrawableGroup_name); + } + a.recycle(); + } + + public void inflate(Resources res, AttributeSet attrs, Theme theme) { + final TypedArray a = obtainAttributes(res, theme, attrs, R.styleable.VectorDrawableGroup); + final int[] themeAttrs = a.extractThemeAttrs(); + + mThemeAttrs = themeAttrs; + // NOTE: The set of attributes loaded here MUST match the + // set of attributes loaded in applyTheme. + + if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawableGroup_rotation] == 0) { + mRotate = a.getFloat(R.styleable.VectorDrawableGroup_rotation, mRotate); + } + + if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawableGroup_pivotX] == 0) { + mPivotX = a.getFloat(R.styleable.VectorDrawableGroup_pivotX, mPivotX); + } + + if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawableGroup_pivotY] == 0) { + mPivotY = a.getFloat(R.styleable.VectorDrawableGroup_pivotY, mPivotY); + } + + if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawableGroup_scaleX] == 0) { + mScaleX = a.getFloat(R.styleable.VectorDrawableGroup_scaleX, mScaleX); + } + + if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawableGroup_scaleY] == 0) { + mScaleY = a.getFloat(R.styleable.VectorDrawableGroup_scaleY, mScaleY); + } + + if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawableGroup_translateX] == 0) { + mTranslateX = a.getFloat(R.styleable.VectorDrawableGroup_translateX, mTranslateX); + } + + if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawableGroup_translateY] == 0) { + mTranslateY = a.getFloat(R.styleable.VectorDrawableGroup_translateY, mTranslateY); + } + + if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawableGroup_name] == 0) { + mGroupName = a.getString(R.styleable.VectorDrawableGroup_name); + } + + if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawableGroup_alpha] == 0) { + mGroupAlpha = a.getFloat(R.styleable.VectorDrawableGroup_alpha, mGroupAlpha); + } + + updateLocalMatrix(); + a.recycle(); + } + + private void updateLocalMatrix() { + // The order we apply is the same as the + // RenderNode.cpp::applyViewPropertyTransforms(). + mLocalMatrix.reset(); + mLocalMatrix.postTranslate(-mPivotX, -mPivotY); + mLocalMatrix.postScale(mScaleX, mScaleY); + mLocalMatrix.postRotate(mRotate, 0, 0); + mLocalMatrix.postTranslate(mTranslateX + mPivotX, mTranslateY + mPivotY); + } + /** * Must return in order of adding * @return ordered list of paths */ - public Collection<VPath> getPaths() { - return mVGList; + public ArrayList<VPath> getPaths() { + return mPathList; } } private static class VPath { - private static final int MAX_STATES = 10; - private int[] mThemeAttrs; int mStrokeColor = 0; float mStrokeWidth = 0; float mStrokeOpacity = Float.NaN; - - int mFillColor = 0; + int mFillColor = Color.BLACK; int mFillRule; float mFillOpacity = Float.NaN; - - float mRotate = 0; - float mPivotX = 0; - float mPivotY = 0; - float mTrimPathStart = 0; float mTrimPathEnd = 1; float mTrimPathOffset = 0; @@ -612,29 +956,22 @@ public class VectorDrawable extends Drawable { Paint.Join mStrokeLineJoin = Paint.Join.MITER; float mStrokeMiterlimit = 4; - private VNode[] mNode = null; - private String mId; - private int[] mCheckState = new int[MAX_STATES]; - private boolean[] mCheckValue = new boolean[MAX_STATES]; - private int mNumberOfStates = 0; + private PathParser.PathDataNode[] mNode = null; + private String mPathName; public VPath() { // Empty constructor. } - public VPath(VPath p) { - copyFrom(p); - } - public void toPath(Path path) { path.reset(); if (mNode != null) { - VNode.createPath(mNode, path); + PathParser.PathDataNode.nodesToPath(mNode, path); } } - public String getID() { - return mId; + public String getPathName() { + return mPathName; } private Paint.Cap getStrokeLineCap(int id, Paint.Cap defValue) { @@ -663,6 +1000,102 @@ public class VectorDrawable extends Drawable { } } + /* Setters and Getters, mostly used by animator from AnimatedVectorDrawable. */ + @SuppressWarnings("unused") + public PathParser.PathDataNode[] getPathData() { + return mNode; + } + + @SuppressWarnings("unused") + public void setPathData(PathParser.PathDataNode[] node) { + if (!PathParser.canMorph(mNode, node)) { + // This should not happen in the middle of animation. + mNode = PathParser.deepCopyNodes(node); + } else { + PathParser.updateNodes(mNode, node); + } + } + + @SuppressWarnings("unused") + int getStroke() { + return mStrokeColor; + } + + @SuppressWarnings("unused") + void setStroke(int strokeColor) { + mStrokeColor = strokeColor; + } + + @SuppressWarnings("unused") + float getStrokeWidth() { + return mStrokeWidth; + } + + @SuppressWarnings("unused") + void setStrokeWidth(float strokeWidth) { + mStrokeWidth = strokeWidth; + } + + @SuppressWarnings("unused") + float getStrokeOpacity() { + return mStrokeOpacity; + } + + @SuppressWarnings("unused") + void setStrokeOpacity(float strokeOpacity) { + mStrokeOpacity = strokeOpacity; + } + + @SuppressWarnings("unused") + int getFill() { + return mFillColor; + } + + @SuppressWarnings("unused") + void setFill(int fillColor) { + mFillColor = fillColor; + } + + @SuppressWarnings("unused") + float getFillOpacity() { + return mFillOpacity; + } + + @SuppressWarnings("unused") + void setFillOpacity(float fillOpacity) { + mFillOpacity = fillOpacity; + } + + @SuppressWarnings("unused") + float getTrimPathStart() { + return mTrimPathStart; + } + + @SuppressWarnings("unused") + void setTrimPathStart(float trimPathStart) { + mTrimPathStart = trimPathStart; + } + + @SuppressWarnings("unused") + float getTrimPathEnd() { + return mTrimPathEnd; + } + + @SuppressWarnings("unused") + void setTrimPathEnd(float trimPathEnd) { + mTrimPathEnd = trimPathEnd; + } + + @SuppressWarnings("unused") + float getTrimPathOffset() { + return mTrimPathOffset; + } + + @SuppressWarnings("unused") + void setTrimPathOffset(float trimPathOffset) { + mTrimPathOffset = trimPathOffset; + } + public void inflate(Resources r, AttributeSet attrs, Theme theme) { final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.VectorDrawablePath); final int[] themeAttrs = a.extractThemeAttrs(); @@ -675,11 +1108,12 @@ public class VectorDrawable extends Drawable { } if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_name] == 0) { - mId = a.getString(R.styleable.VectorDrawablePath_name); + mPathName = a.getString(R.styleable.VectorDrawablePath_name); } if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_pathData] == 0) { - mNode = parsePath(a.getString(R.styleable.VectorDrawablePath_pathData)); + mNode = PathParser.createNodesFromPathData(a.getString( + R.styleable.VectorDrawablePath_pathData)); } if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_fill] == 0) { @@ -690,18 +1124,6 @@ public class VectorDrawable extends Drawable { mFillOpacity = a.getFloat(R.styleable.VectorDrawablePath_fillOpacity, mFillOpacity); } - if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_rotation] == 0) { - mRotate = a.getFloat(R.styleable.VectorDrawablePath_rotation, mRotate); - } - - if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_pivotX] == 0) { - mPivotX = a.getFloat(R.styleable.VectorDrawablePath_pivotX, mPivotX); - } - - if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_pivotY] == 0) { - mPivotY = a.getFloat(R.styleable.VectorDrawablePath_pivotY, mPivotY); - } - if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_strokeLineCap] == 0) { mStrokeLineCap = getStrokeLineCap( @@ -770,20 +1192,17 @@ public class VectorDrawable extends Drawable { mClip = a.getBoolean(R.styleable.VectorDrawablePath_clipToPath, mClip); if (a.hasValue(R.styleable.VectorDrawablePath_name)) { - mId = a.getString(R.styleable.VectorDrawablePath_name); + mPathName = a.getString(R.styleable.VectorDrawablePath_name); } if (a.hasValue(R.styleable.VectorDrawablePath_pathData)) { - mNode = parsePath(a.getString(R.styleable.VectorDrawablePath_pathData)); + mNode = PathParser.createNodesFromPathData(a.getString( + R.styleable.VectorDrawablePath_pathData)); } mFillColor = a.getColor(R.styleable.VectorDrawablePath_fill, mFillColor); mFillOpacity = a.getFloat(R.styleable.VectorDrawablePath_fillOpacity, mFillOpacity); - mRotate = a.getFloat(R.styleable.VectorDrawablePath_rotation, mRotate); - mPivotX = a.getFloat(R.styleable.VectorDrawablePath_pivotX, mPivotX); - mPivotY = a.getFloat(R.styleable.VectorDrawablePath_pivotY, mPivotY); - mStrokeLineCap = getStrokeLineCap(a.getInt( R.styleable.VectorDrawablePath_strokeLineCap, -1), mStrokeLineCap); mStrokeLineJoin = getStrokeLineJoin(a.getInt( @@ -802,528 +1221,17 @@ public class VectorDrawable extends Drawable { R.styleable.VectorDrawablePath_trimPathStart, mTrimPathStart); updateColorAlphas(); + a.recycle(); } private void updateColorAlphas() { if (!Float.isNaN(mFillOpacity)) { - mFillColor &= 0x00FFFFFF; - mFillColor |= ((int) (0xFF * mFillOpacity)) << 24; + mFillColor = applyAlpha(mFillColor, mFillOpacity); } if (!Float.isNaN(mStrokeOpacity)) { - mStrokeColor &= 0x00FFFFFF; - mStrokeColor |= ((int) (0xFF * mStrokeOpacity)) << 24; - } - } - - private static int nextStart(String s, int end) { - char c; - - while (end < s.length()) { - c = s.charAt(end); - if (((c - 'A') * (c - 'Z') <= 0) || (((c - 'a') * (c - 'z') <= 0))) { - return end; - } - end++; - } - return end; - } - - private void addNode(ArrayList<VectorDrawable.VNode> list, char cmd, float[] val) { - list.add(new VectorDrawable.VNode(cmd, val)); - } - - /** - * parse the floats in the string - * this is an optimized version of - * parseFloat(s.split(",|\\s")); - * - * @param s the string containing a command and list of floats - * @return array of floats - */ - private static float[] getFloats(String s) { - if (s.charAt(0) == 'z' | s.charAt(0) == 'Z') { - return new float[0]; - } - try { - float[] tmp = new float[s.length()]; - int count = 0; - int pos = 1, end; - while ((end = extract(s, pos)) >= 0) { - if (pos < end) { - tmp[count++] = Float.parseFloat(s.substring(pos, end)); - } - pos = end + 1; - } - // handle the final float if there is one - if (pos < s.length()) { - tmp[count++] = Float.parseFloat(s.substring(pos, s.length())); - } - return Arrays.copyOf(tmp, count); - } catch (NumberFormatException e){ - Log.e(LOGTAG,"error in parsing \""+s+"\""); - throw e; - } - } - - /** - * calculate the position of the next comma or space - * @param s the string to search - * @param start the position to start searching - * @return the position of the next comma or space or -1 if none found - */ - private static int extract(String s, int start) { - int space = s.indexOf(' ', start); - int comma = s.indexOf(',', start); - if (space == -1) { - return comma; - } - if (comma == -1) { - return space; + mStrokeColor = applyAlpha(mStrokeColor, mStrokeOpacity); } - return (comma > space) ? space : comma; } - - private VectorDrawable.VNode[] parsePath(String value) { - int start = 0; - int end = 1; - - ArrayList<VectorDrawable.VNode> list = new ArrayList<VectorDrawable.VNode>(); - while (end < value.length()) { - end = nextStart(value, end); - String s = value.substring(start, end); - float[] val = getFloats(s); - addNode(list, s.charAt(0), val); - - start = end; - end++; - } - if ((end - start) == 1 && start < value.length()) { - - addNode(list, value.charAt(start), new float[0]); - } - return list.toArray(new VectorDrawable.VNode[list.size()]); - } - - public void copyFrom(VPath p1) { - mNode = new VNode[p1.mNode.length]; - for (int i = 0; i < mNode.length; i++) { - mNode[i] = new VNode(p1.mNode[i]); - } - mId = p1.mId; - mStrokeColor = p1.mStrokeColor; - mFillColor = p1.mFillColor; - mStrokeWidth = p1.mStrokeWidth; - mRotate = p1.mRotate; - mPivotX = p1.mPivotX; - mPivotY = p1.mPivotY; - mTrimPathStart = p1.mTrimPathStart; - mTrimPathEnd = p1.mTrimPathEnd; - mTrimPathOffset = p1.mTrimPathOffset; - mStrokeLineCap = p1.mStrokeLineCap; - mStrokeLineJoin = p1.mStrokeLineJoin; - mStrokeMiterlimit = p1.mStrokeMiterlimit; - mNumberOfStates = p1.mNumberOfStates; - for (int i = 0; i < mNumberOfStates; i++) { - mCheckState[i] = p1.mCheckState[i]; - mCheckValue[i] = p1.mCheckValue[i]; - } - - mFillRule = p1.mFillRule; - } - } - - private static class VNode { - private char mType; - private float[] mParams; - - public VNode(char type, float[] params) { - mType = type; - mParams = params; - } - - public VNode(VNode n) { - mType = n.mType; - mParams = Arrays.copyOf(n.mParams, n.mParams.length); - } - - public static void createPath(VNode[] node, Path path) { - float[] current = new float[4]; - char previousCommand = 'm'; - for (int i = 0; i < node.length; i++) { - addCommand(path, current, previousCommand, node[i].mType, node[i].mParams); - previousCommand = node[i].mType; - } - } - - private static void addCommand(Path path, float[] current, - char previousCmd, char cmd, float[] val) { - - int incr = 2; - float currentX = current[0]; - float currentY = current[1]; - float ctrlPointX = current[2]; - float ctrlPointY = current[3]; - float reflectiveCtrlPointX; - float reflectiveCtrlPointY; - - switch (cmd) { - case 'z': - case 'Z': - path.close(); - return; - case 'm': - case 'M': - case 'l': - case 'L': - case 't': - case 'T': - incr = 2; - break; - case 'h': - case 'H': - case 'v': - case 'V': - incr = 1; - break; - case 'c': - case 'C': - incr = 6; - break; - case 's': - case 'S': - case 'q': - case 'Q': - incr = 4; - break; - case 'a': - case 'A': - incr = 7; - break; - } - for (int k = 0; k < val.length; k += incr) { - switch (cmd) { - case 'm': // moveto - Start a new sub-path (relative) - path.rMoveTo(val[k + 0], val[k + 1]); - currentX += val[k + 0]; - currentY += val[k + 1]; - break; - case 'M': // moveto - Start a new sub-path - path.moveTo(val[k + 0], val[k + 1]); - currentX = val[k + 0]; - currentY = val[k + 1]; - break; - case 'l': // lineto - Draw a line from the current point (relative) - path.rLineTo(val[k + 0], val[k + 1]); - currentX += val[k + 0]; - currentY += val[k + 1]; - break; - case 'L': // lineto - Draw a line from the current point - path.lineTo(val[k + 0], val[k + 1]); - currentX = val[k + 0]; - currentY = val[k + 1]; - break; - case 'z': // closepath - Close the current subpath - case 'Z': // closepath - Close the current subpath - path.close(); - break; - case 'h': // horizontal lineto - Draws a horizontal line (relative) - path.rLineTo(val[k + 0], 0); - currentX += val[k + 0]; - break; - case 'H': // horizontal lineto - Draws a horizontal line - path.lineTo(val[k + 0], currentY); - currentX = val[k + 0]; - break; - case 'v': // vertical lineto - Draws a vertical line from the current point (r) - path.rLineTo(0, val[k + 0]); - currentY += val[k + 0]; - break; - case 'V': // vertical lineto - Draws a vertical line from the current point - path.lineTo(currentX, val[k + 0]); - currentY = val[k + 0]; - break; - case 'c': // curveto - Draws a cubic Bézier curve (relative) - path.rCubicTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3], - val[k + 4], val[k + 5]); - - ctrlPointX = currentX + val[k + 2]; - ctrlPointY = currentY + val[k + 3]; - currentX += val[k + 4]; - currentY += val[k + 5]; - - break; - case 'C': // curveto - Draws a cubic Bézier curve - path.cubicTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3], - val[k + 4], val[k + 5]); - currentX = val[k + 4]; - currentY = val[k + 5]; - ctrlPointX = val[k + 2]; - ctrlPointY = val[k + 3]; - break; - case 's': // smooth curveto - Draws a cubic Bézier curve (reflective cp) - reflectiveCtrlPointX = 0; - reflectiveCtrlPointY = 0; - if (previousCmd == 'c' || previousCmd == 's' - || previousCmd == 'C' || previousCmd == 'S') { - reflectiveCtrlPointX = currentX - ctrlPointX; - reflectiveCtrlPointY = currentY - ctrlPointY; - } - path.rCubicTo(reflectiveCtrlPointX, reflectiveCtrlPointY, - val[k + 0], val[k + 1], - val[k + 2], val[k + 3]); - - ctrlPointX = currentX + val[k + 0]; - ctrlPointY = currentY + val[k + 1]; - currentX += val[k + 2]; - currentY += val[k + 3]; - break; - case 'S': // shorthand/smooth curveto Draws a cubic Bézier curve(reflective cp) - reflectiveCtrlPointX = currentX; - reflectiveCtrlPointY = currentY; - if (previousCmd == 'c' || previousCmd == 's' - || previousCmd == 'C' || previousCmd == 'S') { - reflectiveCtrlPointX = 2 * currentX - ctrlPointX; - reflectiveCtrlPointY = 2 * currentY - ctrlPointY; - } - path.cubicTo(reflectiveCtrlPointX, reflectiveCtrlPointY, - val[k + 0], val[k + 1], val[k + 2], val[k + 3]); - ctrlPointX = val[k + 0]; - ctrlPointY = val[k + 1]; - currentX = val[k + 2]; - currentY = val[k + 3]; - break; - case 'q': // Draws a quadratic Bézier (relative) - path.rQuadTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3]); - ctrlPointX = currentX + val[k + 0]; - ctrlPointY = currentY + val[k + 1]; - currentX += val[k + 2]; - currentY += val[k + 3]; - break; - case 'Q': // Draws a quadratic Bézier - path.quadTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3]); - ctrlPointX = val[k + 0]; - ctrlPointY = val[k + 1]; - currentX = val[k + 2]; - currentY = val[k + 3]; - break; - case 't': // Draws a quadratic Bézier curve(reflective control point)(relative) - reflectiveCtrlPointX = 0; - reflectiveCtrlPointY = 0; - if (previousCmd == 'q' || previousCmd == 't' - || previousCmd == 'Q' || previousCmd == 'T') { - reflectiveCtrlPointX = currentX - ctrlPointX; - reflectiveCtrlPointY = currentY - ctrlPointY; - } - path.rQuadTo(reflectiveCtrlPointX, reflectiveCtrlPointY, - val[k + 0], val[k + 1]); - ctrlPointX = currentX + reflectiveCtrlPointX; - ctrlPointY = currentY + reflectiveCtrlPointY; - currentX += val[k + 0]; - currentY += val[k + 1]; - break; - case 'T': // Draws a quadratic Bézier curve (reflective control point) - reflectiveCtrlPointX = currentX; - reflectiveCtrlPointY = currentY; - if (previousCmd == 'q' || previousCmd == 't' - || previousCmd == 'Q' || previousCmd == 'T') { - reflectiveCtrlPointX = 2 * currentX - ctrlPointX; - reflectiveCtrlPointY = 2 * currentY - ctrlPointY; - } - path.quadTo(reflectiveCtrlPointX, reflectiveCtrlPointY, - val[k + 0], val[k + 1]); - ctrlPointX = reflectiveCtrlPointX; - ctrlPointY = reflectiveCtrlPointY; - currentX = val[k + 0]; - currentY = val[k + 1]; - break; - case 'a': // Draws an elliptical arc - // (rx ry x-axis-rotation large-arc-flag sweep-flag x y) - drawArc(path, - currentX, - currentY, - val[k + 5] + currentX, - val[k + 6] + currentY, - val[k + 0], - val[k + 1], - val[k + 2], - val[k + 3] != 0, - val[k + 4] != 0); - currentX += val[k + 5]; - currentY += val[k + 6]; - ctrlPointX = currentX; - ctrlPointY = currentY; - break; - case 'A': // Draws an elliptical arc - drawArc(path, - currentX, - currentY, - val[k + 5], - val[k + 6], - val[k + 0], - val[k + 1], - val[k + 2], - val[k + 3] != 0, - val[k + 4] != 0); - currentX = val[k + 5]; - currentY = val[k + 6]; - ctrlPointX = currentX; - ctrlPointY = currentY; - break; - } - previousCmd = cmd; - } - current[0] = currentX; - current[1] = currentY; - current[2] = ctrlPointX; - current[3] = ctrlPointY; - } - - private static void drawArc(Path p, - float x0, - float y0, - float x1, - float y1, - float a, - float b, - float theta, - boolean isMoreThanHalf, - boolean isPositiveArc) { - - /* Convert rotation angle from degrees to radians */ - double thetaD = Math.toRadians(theta); - /* Pre-compute rotation matrix entries */ - double cosTheta = Math.cos(thetaD); - double sinTheta = Math.sin(thetaD); - /* Transform (x0, y0) and (x1, y1) into unit space */ - /* using (inverse) rotation, followed by (inverse) scale */ - double x0p = (x0 * cosTheta + y0 * sinTheta) / a; - double y0p = (-x0 * sinTheta + y0 * cosTheta) / b; - double x1p = (x1 * cosTheta + y1 * sinTheta) / a; - double y1p = (-x1 * sinTheta + y1 * cosTheta) / b; - - /* Compute differences and averages */ - double dx = x0p - x1p; - double dy = y0p - y1p; - double xm = (x0p + x1p) / 2; - double ym = (y0p + y1p) / 2; - /* Solve for intersecting unit circles */ - double dsq = dx * dx + dy * dy; - if (dsq == 0.0) { - Log.w(LOGTAG, " Points are coincident"); - return; /* Points are coincident */ - } - double disc = 1.0 / dsq - 1.0 / 4.0; - if (disc < 0.0) { - Log.w(LOGTAG, "Points are too far apart " + dsq); - float adjust = (float) (Math.sqrt(dsq) / 1.99999); - drawArc(p, x0, y0, x1, y1, a * adjust, - b * adjust, theta, isMoreThanHalf, isPositiveArc); - return; /* Points are too far apart */ - } - double s = Math.sqrt(disc); - double sdx = s * dx; - double sdy = s * dy; - double cx; - double cy; - if (isMoreThanHalf == isPositiveArc) { - cx = xm - sdy; - cy = ym + sdx; - } else { - cx = xm + sdy; - cy = ym - sdx; - } - - double eta0 = Math.atan2((y0p - cy), (x0p - cx)); - - double eta1 = Math.atan2((y1p - cy), (x1p - cx)); - - double sweep = (eta1 - eta0); - if (isPositiveArc != (sweep >= 0)) { - if (sweep > 0) { - sweep -= 2 * Math.PI; - } else { - sweep += 2 * Math.PI; - } - } - - cx *= a; - cy *= b; - double tcx = cx; - cx = cx * cosTheta - cy * sinTheta; - cy = tcx * sinTheta + cy * cosTheta; - - arcToBezier(p, cx, cy, a, b, x0, y0, thetaD, eta0, sweep); - } - - /** - * Converts an arc to cubic Bezier segments and records them in p. - * - * @param p The target for the cubic Bezier segments - * @param cx The x coordinate center of the ellipse - * @param cy The y coordinate center of the ellipse - * @param a The radius of the ellipse in the horizontal direction - * @param b The radius of the ellipse in the vertical direction - * @param e1x E(eta1) x coordinate of the starting point of the arc - * @param e1y E(eta2) y coordinate of the starting point of the arc - * @param theta The angle that the ellipse bounding rectangle makes with horizontal plane - * @param start The start angle of the arc on the ellipse - * @param sweep The angle (positive or negative) of the sweep of the arc on the ellipse - */ - private static void arcToBezier(Path p, - double cx, - double cy, - double a, - double b, - double e1x, - double e1y, - double theta, - double start, - double sweep) { - // Taken from equations at: http://spaceroots.org/documents/ellipse/node8.html - // and http://www.spaceroots.org/documents/ellipse/node22.html - - // Maximum of 45 degrees per cubic Bezier segment - int numSegments = Math.abs((int) Math.ceil(sweep * 4 / Math.PI)); - - double eta1 = start; - double cosTheta = Math.cos(theta); - double sinTheta = Math.sin(theta); - double cosEta1 = Math.cos(eta1); - double sinEta1 = Math.sin(eta1); - double ep1x = (-a * cosTheta * sinEta1) - (b * sinTheta * cosEta1); - double ep1y = (-a * sinTheta * sinEta1) + (b * cosTheta * cosEta1); - - double anglePerSegment = sweep / numSegments; - for (int i = 0; i < numSegments; i++) { - double eta2 = eta1 + anglePerSegment; - double sinEta2 = Math.sin(eta2); - double cosEta2 = Math.cos(eta2); - double e2x = cx + (a * cosTheta * cosEta2) - (b * sinTheta * sinEta2); - double e2y = cy + (a * sinTheta * cosEta2) + (b * cosTheta * sinEta2); - double ep2x = -a * cosTheta * sinEta2 - b * sinTheta * cosEta2; - double ep2y = -a * sinTheta * sinEta2 + b * cosTheta * cosEta2; - double tanDiff2 = Math.tan((eta2 - eta1) / 2); - double alpha = - Math.sin(eta2 - eta1) * (Math.sqrt(4 + (3 * tanDiff2 * tanDiff2)) - 1) / 3; - double q1x = e1x + alpha * ep1x; - double q1y = e1y + alpha * ep1y; - double q2x = e2x - alpha * ep2x; - double q2y = e2y - alpha * ep2y; - - p.cubicTo((float) q1x, - (float) q1y, - (float) q2x, - (float) q2y, - (float) e2x, - (float) e2y); - eta1 = eta2; - e1x = e2x; - e1y = e2y; - ep1x = ep2x; - ep1y = ep2y; - } - } - } } diff --git a/graphics/java/android/graphics/pdf/PdfRenderer.java b/graphics/java/android/graphics/pdf/PdfRenderer.java index 39795b5..b63edce 100644 --- a/graphics/java/android/graphics/pdf/PdfRenderer.java +++ b/graphics/java/android/graphics/pdf/PdfRenderer.java @@ -188,6 +188,7 @@ public final class PdfRenderer implements AutoCloseable { private void doClose() { if (mCurrentPage != null) { mCurrentPage.close(); + mCurrentPage = null; } nativeClose(mNativeDocument); try { @@ -374,7 +375,6 @@ public final class PdfRenderer implements AutoCloseable { nativeClosePage(mNativePage); mNativePage = 0; mCloseGuard.close(); - mCurrentPage = null; } private void throwIfClosed() { |