From 9a347f199284ad8bcb8a81bfbd306fe0b1a710ba Mon Sep 17 00:00:00 2001 From: Chris Craik Date: Fri, 27 Jun 2014 17:23:47 -0700 Subject: [PATCH] Initial replacement of setOutline() with ViewOutlineProvider API bug:15283203 A View's outline is now managed by its outline provider. This means the outline is automatically requeried when needed (e.g. drawable updates or resize), with customizable querying behavior. Also adds 'isFilled' property to outline, to be used for hinting shadow overdraw avoidance. Change-Id: Ie137548fa850f1ff7863ab2f660d05145c2ad11e --- api/current.txt | 13 +- core/java/android/view/RenderNode.java | 36 ++--- core/java/android/view/View.java | 125 +++++++++++------- .../android/view/ViewOutlineProvider.java | 57 ++++++++ graphics/java/android/graphics/Outline.java | 62 ++++++--- .../android/graphics/drawable/Drawable.java | 9 +- 6 files changed, 204 insertions(+), 98 deletions(-) create mode 100644 core/java/android/view/ViewOutlineProvider.java 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());