diff --git a/api/current.txt b/api/current.txt index c962c0ae22fef..bc05bef11d821 100644 --- a/api/current.txt +++ b/api/current.txt @@ -10643,9 +10643,11 @@ package android.graphics { ctor public Outline(android.graphics.Outline); method public boolean canClip(); method public boolean isEmpty(); + method public boolean isFilled(); method public void set(android.graphics.Outline); method public void setConvexPath(android.graphics.Path); method public void setEmpty(); + method public void setFilled(boolean); method public void setOval(int, int, int, int); method public void setOval(android.graphics.Rect); method public void setRect(int, int, int, int); @@ -32731,6 +32733,7 @@ package android.view { method public int getNextFocusRightId(); method public int getNextFocusUpId(); method public android.view.View.OnFocusChangeListener getOnFocusChangeListener(); + method public android.view.ViewOutlineProvider getOutlineProvider(); method public int getOverScrollMode(); method public android.view.ViewOverlay getOverlay(); method public int getPaddingBottom(); @@ -32806,6 +32809,7 @@ package android.view { method public void invalidate(int, int, int, int); method public void invalidate(); method public void invalidateDrawable(android.graphics.drawable.Drawable); + method public void invalidateOutline(); method public boolean isAccessibilityFocused(); method public boolean isActivated(); method public boolean isAttachedToWindow(); @@ -33003,7 +33007,8 @@ package android.view { method public void setOnLongClickListener(android.view.View.OnLongClickListener); method public void setOnSystemUiVisibilityChangeListener(android.view.View.OnSystemUiVisibilityChangeListener); method public void setOnTouchListener(android.view.View.OnTouchListener); - method public void setOutline(android.graphics.Outline); + method public deprecated void setOutline(android.graphics.Outline); + method public void setOutlineProvider(android.view.ViewOutlineProvider); method public void setOverScrollMode(int); method public void setPadding(int, int, int, int); method public void setPaddingRelative(int, int, int, int); @@ -33549,6 +33554,12 @@ package android.view { method public abstract void updateViewLayout(android.view.View, android.view.ViewGroup.LayoutParams); } + public abstract class ViewOutlineProvider { + ctor public ViewOutlineProvider(); + method public abstract boolean getOutline(android.view.View, android.graphics.Outline); + field public static final android.view.ViewOutlineProvider BACKGROUND; + } + public class ViewOverlay { method public void add(android.graphics.drawable.Drawable); method public void clear(); diff --git a/core/java/android/view/RenderNode.java b/core/java/android/view/RenderNode.java index ddc185cfcef4e..0a1204d7f78a5 100644 --- a/core/java/android/view/RenderNode.java +++ b/core/java/android/view/RenderNode.java @@ -126,12 +126,10 @@ import android.graphics.Paint; public class RenderNode { /** * Flag used when calling - * {@link HardwareCanvas#drawDisplayList(RenderNode, android.graphics.Rect, int)} + * {@link HardwareCanvas#drawRenderNode(RenderNode, android.graphics.Rect, int)} * When this flag is set, draw operations lying outside of the bounds of the * display list will be culled early. It is recommeneded to always set this * flag. - * - * @hide */ public static final int FLAG_CLIP_CHILDREN = 0x1; @@ -140,37 +138,29 @@ public class RenderNode { /** * Indicates that the display list is done drawing. * - * @see HardwareCanvas#drawDisplayList(RenderNode, android.graphics.Rect, int) - * - * @hide + * @see HardwareCanvas#drawRenderNode(RenderNode, android.graphics.Rect, int) */ public static final int STATUS_DONE = 0x0; /** * Indicates that the display list needs another drawing pass. * - * @see HardwareCanvas#drawDisplayList(RenderNode, android.graphics.Rect, int) - * - * @hide + * @see HardwareCanvas#drawRenderNode(RenderNode, android.graphics.Rect, int) */ public static final int STATUS_DRAW = 0x1; /** * Indicates that the display list needs to re-execute its GL functors. * - * @see HardwareCanvas#drawDisplayList(RenderNode, android.graphics.Rect, int) + * @see HardwareCanvas#drawRenderNode(RenderNode, android.graphics.Rect, int) * @see HardwareCanvas#callDrawGLFunction(long) - * - * @hide */ public static final int STATUS_INVOKE = 0x2; /** * Indicates that the display list performed GL drawing operations. * - * @see HardwareCanvas#drawDisplayList(RenderNode, android.graphics.Rect, int) - * - * @hide + * @see HardwareCanvas#drawRenderNode(RenderNode, android.graphics.Rect, int) */ public static final int STATUS_DREW = 0x4; @@ -189,14 +179,12 @@ public class RenderNode { } /** - * Creates a new display list that can be used to record batches of - * drawing operations. + * Creates a new RenderNode that can be used to record batches of + * drawing operations, and store / apply render properties when drawn. * - * @param name The name of the display list, used for debugging purpose. May be null. + * @param name The name of the RenderNode, used for debugging purpose. May be null. * - * @return A new display list. - * - * @hide + * @return A new RenderNode. */ public static RenderNode create(String name) { return new RenderNode(name); @@ -263,8 +251,6 @@ public class RenderNode { * Reset native resources. This is called when cleaning up the state of display lists * during destruction of hardware resources, to ensure that we do not hold onto * obsolete resources after related resources are gone. - * - * @hide */ public void destroyDisplayListData() { if (!mValid) return; @@ -409,8 +395,6 @@ public class RenderNode { * for the matrix parameter. * * @param matrix The matrix, null indicates that the matrix should be cleared. - * - * @hide */ public boolean setAnimationMatrix(Matrix matrix) { return nSetAnimationMatrix(mNativeRenderNode, @@ -838,8 +822,6 @@ public class RenderNode { /** * Outputs the display list to the log. This method exists for use by * tools to output display lists for selected nodes to the log. - * - * @hide */ public void output() { nOutput(mNativeRenderNode); diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 829e08971e930..d11d17e238936 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -2379,9 +2379,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ static final int PFLAG3_CALLED_SUPER = 0x10; - /** - * Flag indicating that a view's outline has been specifically defined. - */ + @Deprecated static final int PFLAG3_OUTLINE_DEFINED = 0x20; /** @@ -3275,11 +3273,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, private int[] mDrawableState = null; - /** - * Stores the outline of the view, passed down to the DisplayList level for - * defining shadow shape. - */ + @Deprecated private Outline mOutline; + ViewOutlineProvider mOutlineProvider = ViewOutlineProvider.BACKGROUND; /** * Animator that automatically runs based on state changes. @@ -10757,24 +10753,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } } - /** - * Sets the {@link Outline} of the view, which defines the shape of the shadow it - * casts, and enables outline clipping. - *

- * By default, a View queries its Outline from its background drawable, via - * {@link Drawable#getOutline(Outline)}. Manually setting the Outline with this method allows - * this behavior to be overridden. - *

- * If the outline is {@link Outline#isEmpty()} or is null, - * shadows will not be cast. - *

- * Only outlines that return true from {@link Outline#canClip()} may be used for clipping. - * - * @param outline The new outline of the view. - * - * @see #setClipToOutline(boolean) - * @see #getClipToOutline() - */ + @Deprecated public void setOutline(@Nullable Outline outline) { mPrivateFlags3 |= PFLAG3_OUTLINE_DEFINED; @@ -10798,7 +10777,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * Note that this flag will only be respected if the View's Outline returns true from * {@link Outline#canClip()}. * - * @see #setOutline(Outline) + * @see #setOutlineProvider(ViewOutlineProvider) * @see #setClipToOutline(boolean) */ public final boolean getClipToOutline() { @@ -10811,7 +10790,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * Note that this flag will only be respected if the View's Outline returns true from * {@link Outline#canClip()}. * - * @see #setOutline(Outline) + * @see #setOutlineProvider(ViewOutlineProvider) * @see #getClipToOutline() */ public void setClipToOutline(boolean clipToOutline) { @@ -10821,25 +10800,74 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } } - private void queryOutlineFromBackgroundIfUndefined() { - if ((mPrivateFlags3 & PFLAG3_OUTLINE_DEFINED) == 0) { - // Outline not currently defined, query from background - if (mOutline == null) { - mOutline = new Outline(); - } else { - //invalidate outline, to ensure background calculates it - mOutline.setEmpty(); - } - if (mBackground.getOutline(mOutline)) { - if (mOutline.isEmpty()) { - throw new IllegalStateException("Background drawable failed to build outline"); + /** + * Sets the {@link ViewOutlineProvider} of the view, which generates the Outline that defines + * the shape of the shadow it casts, and enables outline clipping. + *

+ * The default ViewOutlineProvider, {@link ViewOutlineProvider#BACKGROUND}, queries the Outline + * from the View's background drawable, via {@link Drawable#getOutline(Outline)}. Changing the + * outline provider with this method allows this behavior to be overridden. + *

+ * If the ViewOutlineProvider is null, if querying it for an outline returns false, + * or if the produced Outline is {@link Outline#isEmpty()}, shadows will not be cast. + *

+ * Only outlines that return true from {@link Outline#canClip()} may be used for clipping. + * + * @see #setClipToOutline(boolean) + * @see #getClipToOutline() + * @see #getOutlineProvider() + */ + public void setOutlineProvider(ViewOutlineProvider provider) { + mOutlineProvider = provider; + invalidateOutline(); + } + + /** + * Returns the current {@link ViewOutlineProvider} of the view, which generates the Outline + * that defines the shape of the shadow it casts, and enables outline clipping. + * + * @see #setOutlineProvider(ViewOutlineProvider) + */ + public ViewOutlineProvider getOutlineProvider() { + return mOutlineProvider; + } + + /** + * Called to rebuild this View's Outline from its {@link ViewOutlineProvider outline provider} + * + * @see #setOutlineProvider(ViewOutlineProvider) + */ + public void invalidateOutline() { + if ((mPrivateFlags3 & PFLAG3_OUTLINE_DEFINED) != 0) { + // TODO: remove this when removing old outline code + // setOutline() was called to manually set outline, ignore provider + return; + } + + // Unattached views ignore this signal, and outline is recomputed in onAttachedToWindow() + if (mAttachInfo == null) return; + + final Outline outline = mAttachInfo.mTmpOutline; + outline.setEmpty(); + + if (mOutlineProvider == null) { + // no provider, remove outline + mRenderNode.setOutline(null); + } else { + if (mOutlineProvider.getOutline(this, outline)) { + if (outline.isEmpty()) { + throw new IllegalStateException("Outline provider failed to build outline"); } - mRenderNode.setOutline(mOutline); + // provider has provided + mRenderNode.setOutline(outline); } else { + // provider failed to provide mRenderNode.setOutline(null); } - notifySubtreeAccessibilityStateChangedIfNeeded(); } + + notifySubtreeAccessibilityStateChangedIfNeeded(); + invalidateViewProperty(false, false); } /** @@ -12670,6 +12698,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, resetSubtreeAccessibilityStateChanged(); + invalidateOutline(); + if (isFocused()) { InputMethodManager imm = InputMethodManager.peekInstance(); imm.focusIn(this); @@ -14967,7 +14997,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (mBackgroundSizeChanged) { background.setBounds(0, 0, mRight - mLeft, mBottom - mTop); mBackgroundSizeChanged = false; - queryOutlineFromBackgroundIfUndefined(); } // Attempt to use a display list if requested. @@ -15012,7 +15041,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @param renderNode Existing RenderNode, or {@code null} * @return A valid display list for the specified drawable */ - private RenderNode getDrawableRenderNode(Drawable drawable, RenderNode renderNode) { + private static RenderNode getDrawableRenderNode(Drawable drawable, RenderNode renderNode) { if (renderNode == null) { renderNode = RenderNode.create(drawable.getClass().getName()); } @@ -15342,6 +15371,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mOverlay.getOverlayView().setRight(newWidth); mOverlay.getOverlayView().setBottom(newHeight); } + invalidateOutline(); } /** @@ -15378,9 +15408,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, invalidate(dirty.left + scrollX, dirty.top + scrollY, dirty.right + scrollX, dirty.bottom + scrollY); - if (drawable == mBackground) { - queryOutlineFromBackgroundIfUndefined(); - } + invalidateOutline(); } } @@ -19959,6 +19987,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ final Transformation mTmpTransformation = new Transformation(); + /** + * Temporary for use in querying outlines from OutlineProviders + */ + final Outline mTmpOutline = new Outline(); + /** * Temporary list for use in collecting focusable descendents of a view. */ diff --git a/core/java/android/view/ViewOutlineProvider.java b/core/java/android/view/ViewOutlineProvider.java new file mode 100644 index 0000000000000..9c154d5004e37 --- /dev/null +++ b/core/java/android/view/ViewOutlineProvider.java @@ -0,0 +1,57 @@ +/* + * 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.view; + +import android.graphics.Outline; +import android.graphics.drawable.Drawable; + +/** + * Interface by which a View builds its {@link Outline}, used for shadow casting and clipping. + */ +public abstract class ViewOutlineProvider { + /** + * Default outline provider for Views, which queries the Outline from the View's background, + * or returns false if the View does not have a background. + * + * @see Drawable#getOutline(Outline) + */ + public static final ViewOutlineProvider BACKGROUND = new ViewOutlineProvider() { + @Override + public boolean getOutline(View view, Outline outline) { + Drawable background = view.getBackground(); + if (background == null) { + // no background, no outline + return false; + } + return background.getOutline(outline); + } + }; + + /** + * Called to get the provider to populate the Outline. + * + * This method will be called by a View when its owned Drawables are invalidated, when the + * View's size changes, or if {@link View#invalidateOutline()} is called + * explicitly. + * + * @param view The view building the outline. + * @param outline The empty outline to be populated. + * @return true if this View should have an outline, else false. The outline must be + * populated by this method, and non-empty if true is returned. + */ + public abstract boolean getOutline(View view, Outline outline); +} diff --git a/graphics/java/android/graphics/Outline.java b/graphics/java/android/graphics/Outline.java index d87c3cb74ba7a..4bee5543e2ba7 100644 --- a/graphics/java/android/graphics/Outline.java +++ b/graphics/java/android/graphics/Outline.java @@ -17,7 +17,6 @@ package android.graphics; import android.annotation.NonNull; -import android.annotation.Nullable; import android.graphics.drawable.Drawable; import android.view.View; @@ -32,13 +31,15 @@ import android.view.View; */ public final class Outline { /** @hide */ - public Rect mRect; + public Path mPath; + /** @hide */ + public Rect mRect; /** @hide */ public float mRadius; /** @hide */ - public Path mPath; + public boolean mIsFilled; /** * Constructs an empty Outline. Call one of the setter methods to make @@ -59,9 +60,10 @@ public final class Outline { * @see #isEmpty() */ public void setEmpty() { - mRadius = 0; - mRect = null; mPath = null; + mRect = null; + mRadius = 0; + mIsFilled = true; } /** @@ -76,6 +78,36 @@ public final class Outline { return mRect == null && mPath == null; } + + /** + * Returns whether the outline can be used to clip a View. + * + * Currently, only outlines that can be represented as a rectangle, circle, or round rect + * support clipping. + * + * @see {@link View#setClipToOutline(boolean)} + */ + public boolean canClip() { + return !isEmpty() && mRect != null; + } + + /** + * Sets whether the outline represents a fully opaque area. + * + * A filled outline is assumed, by the drawing system, to fully cover content beneath it, + * meaning content beneath may be optimized away. + */ + public void setFilled(boolean isFilled) { + mIsFilled = isFilled; + } + + /** + * Returns whether the outline represents a fully opaque area. + */ + public boolean isFilled() { + return !isEmpty() && mIsFilled; + } + /** * Replace the contents of this Outline with the contents of src. * @@ -96,6 +128,7 @@ public final class Outline { mRect.set(src.mRect); } mRadius = src.mRadius; + mIsFilled = src.mIsFilled; } /** @@ -122,6 +155,7 @@ public final class Outline { mRect.set(left, top, right, bottom); mRadius = radius; mPath = null; + mIsFilled = true; } /** @@ -140,10 +174,11 @@ public final class Outline { setRoundRect(left, top, right, bottom, (bottom - top) / 2.0f); return; } - mRect = null; if (mPath == null) mPath = new Path(); mPath.reset(); mPath.addOval(left, top, right, bottom, Path.Direction.CW); + mRect = null; + mIsFilled = true; } /** @@ -162,20 +197,9 @@ public final class Outline { } if (mPath == null) mPath = new Path(); + mPath.set(convexPath); mRect = null; mRadius = -1.0f; - mPath.set(convexPath); - } - - /** - * Returns whether the outline can be used to clip a View. - * - * Currently, only outlines that can be represented as a rectangle, circle, or round rect - * support clipping. - * - * @see {@link View#setClipToOutline(boolean)} - */ - public boolean canClip() { - return mRect != null; + mIsFilled = true; } } diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java index 40b55a7f0afeb..c4cfd4afe9842 100644 --- a/graphics/java/android/graphics/drawable/Drawable.java +++ b/graphics/java/android/graphics/drawable/Drawable.java @@ -851,11 +851,10 @@ public abstract class Drawable { } /** - * Called to get the drawable to populate the Outline. + * Called to get the drawable to populate the Outline that defines its drawing area. *

- * This method will be called by a View on its background Drawable after bounds change, or its - * Drawable is invalidated, if the View's Outline isn't set explicitly. This allows the - * background Drawable to define the shape of the shadow cast by the View. + * This method is called by the default {@link android.view.ViewOutlineProvider} to define + * the outline of the View. *

* The default behavior defines the outline to be the bounding rectangle. Subclasses that wish * to convey a different shape must override this method. @@ -863,7 +862,7 @@ public abstract class Drawable { * @return true if this drawable actually has an outline, else false. The outline must be * populated by the drawable if true is returned. * - * @see View#setOutline(android.graphics.Outline) + * @see android.view.View#setOutlineProvider(android.view.ViewOutlineProvider) */ public boolean getOutline(@NonNull Outline outline) { outline.setRect(getBounds());